diff --git a/.eslintrc.js b/.eslintrc.js index 31bfd03078..20881d308f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,6 +5,9 @@ const RESTRICTED_MODULES = { { 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' }, ], + patterns: [ + 'icons/*', + ], }; module.exports = { diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3a061040a8..eb0810c8f5 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -22,7 +22,7 @@ jobs: code_quality: name: Code quality 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: - name: Checkout repo uses: actions/checkout@v3 diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 759b120b17..a8ed3ee27a 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -29,5 +29,5 @@ jobs: cleanup_docker_image: uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup_docker.yaml@master with: - dockerImage: prerelease-$GITHUB_REF_NAME_SLUG + dockerImage: review-$GITHUB_REF_NAME_SLUG secrets: inherit diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 94aaac7b1c..e0cb8684b8 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: 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: checks: @@ -24,9 +24,31 @@ jobs: uses: "./.github/workflows/e2e-tests.yml" 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: name: Add pre-release label to issues uses: './.github/workflows/label-issues-in-release.yml' + needs: [ version ] + if: ${{ needs.version.outputs.is_initial == 'true' }} with: tag: ${{ github.ref_name }} label_name: 'pre-release' diff --git a/.github/workflows/upload-source-maps.yml b/.github/workflows/upload-source-maps.yml index 0f5971e766..0c284672a1 100644 --- a/.github/workflows/upload-source-maps.yml +++ b/.github/workflows/upload-source-maps.yml @@ -45,4 +45,4 @@ jobs: run: yarn sentry-cli sourcemaps inject ./.next - name: Upload source maps to Sentry - run: yarn sentry-cli sourcemaps upload --release=${{ github.ref_name }} --validate ./.next \ No newline at end of file + run: yarn sentry-cli sourcemaps upload --release=${{ github.ref_name }} --url-prefix=~/_next/ --validate ./.next \ No newline at end of file diff --git a/.gitignore b/.gitignore index 534aa23650..45e35b7cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ /out/ /public/assets/ /public/envs.js +/public/icons/sprite.svg +/public/icons/README.md /analyze # production diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1a957c87c2..cd1be4e2b7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -265,7 +265,7 @@ }, { "type": "npm", - "script": "format-svg", + "script": "svg:format", "problemMatcher": [], "label": "format svg", "detail": "format svg files with svgo", @@ -318,6 +318,7 @@ "main.L2", "poa_core", "eth_goerli", + "sepolia", "eth", "rootstock", "polygon", diff --git a/Dockerfile b/Dockerfile index e411829719..465491b3c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,6 +58,7 @@ RUN ./collect_envs.sh ./docs/ENVS.md # Build app for production RUN yarn build +RUN yarn svg:build-sprite ### FEATURE REPORTER @@ -89,6 +90,10 @@ WORKDIR /app RUN addgroup --system --gid 1001 nodejs 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/public ./public COPY --from=builder /app/package.json ./package.json diff --git a/README.md b/README.md index a8cdd94e3a..1256043efe 100644 --- a/README.md +++ b/README.md @@ -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). -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 docker run -p 3000:3000 --env-file ghcr.io/blockscout/frontend:latest @@ -29,6 +29,7 @@ See our [Contribution guide](./docs/CONTRIBUTING.md) for pull request protocol. - [Contribution guide](./docs/CONTRIBUTING.md) - [Making a custom build](./docs/CUSTOM_BUILD.md) - [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 diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index ad45f9a4a1..0879fb17d5 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -17,6 +17,7 @@ export { default as sentry } from './sentry'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; export { default as suave } from './suave'; +export { default as txInterpretation } from './txInterpretation'; export { default as web3Wallet } from './web3Wallet'; export { default as verifiedTokens } from './verifiedTokens'; export { default as zkEvmRollup } from './zkEvmRollup'; diff --git a/configs/app/features/txInterpretation.ts b/configs/app/features/txInterpretation.ts new file mode 100644 index 0000000000..c22067ee27 --- /dev/null +++ b/configs/app/features/txInterpretation.ts @@ -0,0 +1,34 @@ +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; diff --git a/configs/app/ui/views/address.ts b/configs/app/ui/views/address.ts index 56789ffad5..c2f0f4fb52 100644 --- a/configs/app/ui/views/address.ts +++ b/configs/app/ui/views/address.ts @@ -27,6 +27,7 @@ const hiddenViews = (() => { const config = Object.freeze({ identiconType, hiddenViews, + solidityscanEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED') === 'true', }); export default config; diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 71dc3fdd4d..75ef364321 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -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_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout #meta NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true diff --git a/configs/envs/.env.eth_goerli b/configs/envs/.env.eth_goerli index bea2e6654e..da1fac4d97 100644 --- a/configs/envs/.env.eth_goerli +++ b/configs/envs/.env.eth_goerli @@ -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_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask'] +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED='true' #meta NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true diff --git a/configs/envs/.env.sepolia b/configs/envs/.env.sepolia new file mode 100644 index 0000000000..b54adc0775 --- /dev/null +++ b/configs/envs/.env.sepolia @@ -0,0 +1,60 @@ +# 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 diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index ac177c95db..0b8b5e3849 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -15,6 +15,7 @@ import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; 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 { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; @@ -393,6 +394,7 @@ const schema = yup .transform(replaceQuotes) .json() .of(yup.string().oneOf(ADDRESS_VIEWS_IDS)), + NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: yup.boolean(), NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: yup .array() .transform(replaceQuotes) @@ -438,6 +440,7 @@ const schema = yup return isNoneSchema.isValidSync(data) || isArrayOfWalletsSchema.isValidSync(data); }), 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().oneOf(SUPPORTED_AD_TEXT_PROVIDERS), NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(), NEXT_PUBLIC_OG_DESCRIPTION: yup.string(), diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index b720f05d2b..dbfd815a82 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -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_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_API_HOST: etc.blockscout.com - NEXT_PUBLIC_STATS_API_HOST: https://stats-etc.k8s.blockscout.com/ + NEXT_PUBLIC_API_HOST: eth-goerli.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_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 @@ -67,6 +67,7 @@ frontend: NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar 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_ADDITIONAL_FIELDS: "['fee_per_gas']" NEXT_PUBLIC_USE_NEXT_JS_PROXY: true diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ded9059c3c..578cbd2129 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -33,7 +33,7 @@ We are using following technology stack in the project - [Yarn](https://yarnpkg.com/) as package manager - [ReactJS](https://reactjs.org/) as UI library - [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 - [Jest](https://jestjs.io/) as JavaScript testing framework - [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 ## Local development -1. Prepare your environment variables: - - 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. 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; -2. Run your local dev server: - - if you picked up option "A" above, use `yarn dev` command - - if your options is "B", use `yarn dev:` command -3. In browser navigate to the URL from the command output (by default, it is `http://localhost:3000`) +To develop locally, follow one of the two paths outlined below: + +A. Custom configuration: + +1. Create `.env.local` file in the root folder and include all required environment variables from the [list](./ENVS.md) +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. +3. Use `yarn dev` command to start the dev server. +4. Open your browser and navigate to the URL provided in the command line 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:` command. +4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`). +   @@ -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 -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. -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 +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 "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 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. *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 | `yarn lint:eslint` | lint project files with ESLint | | `yarn lint:eslint:fix` | lint project files with ESLint and automatically fix problems | | `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** | | `yarn test:jest` | run all Jest unit tests | | `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 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. \ No newline at end of file +And you may find the Dev Container setup useful too. diff --git a/docs/CUSTOM_BUILD.md b/docs/CUSTOM_BUILD.md index ee13d921ac..9142e12846 100644 --- a/docs/CUSTOM_BUILD.md +++ b/docs/CUSTOM_BUILD.md @@ -7,4 +7,4 @@ For running app container from freshly built image do docker run -p 3000:3000 --env-file ``` -*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. diff --git a/docs/ENVS.md b/docs/ENVS.md index c6a6e70645..f1caa77efe 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -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) - [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) + - [Transaction interpretation](ENVS.md#transaction-interpretation) - [Verified tokens info](ENVS.md#verified-tokens-info) - [Bridged tokens](ENVS.md#bridged-tokens) - [Safe{Core} address tags](ENVS.md#safecore-address-tags) @@ -129,7 +130,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` | | 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` | -| 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` |   @@ -189,6 +190,7 @@ Settings for meta tags and OG tags | `burnt_fees` | Burnt fees | | `total_reward` | Total block reward | | `nonce` | Block nonce | +| `miner` | Address of block's miner or validator |   @@ -198,6 +200,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_HIDDEN_VIEWS | `Array` | 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 | Id | Description | @@ -272,7 +275,7 @@ Settings for meta tags and OG tags ## 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 @@ -287,7 +290,7 @@ Settings for meta tags and OG tags ### 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 | | --- | --- | --- | --- | --- | --- | @@ -419,7 +422,7 @@ This feature is **always enabled**, but you can configure its behavior by passin | 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'` | | shortDescription | `string` | Displayed only in the app list. | Required | `'Awesome app'` | -| categories | `Array` | Displayed category. Select one of the following bellow. | Required | `['security', 'tools']` | +| categories | `Array` | Displayed category. Select one of the following below. | Required | `['security', 'tools']` | | 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'` | | description | `string` | Displayed only in the modal dialog with additional info about the app. | Required | `'The best app'` | @@ -472,6 +475,14 @@ This feature is **enabled by default** with the `['metamask']` value. To switch   +### 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` | + +  + ### Verified tokens info | Variable | Type| Description | Compulsoriness | Default value | Example value | @@ -512,13 +523,13 @@ This feature allows users to view tokens that have been bridged from other EVM c ### 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.   ### 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 | | --- | --- | --- | --- | --- | --- | diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..a57bb9602c --- /dev/null +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## 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) diff --git a/icons/collection.svg b/icons/collection.svg new file mode 100644 index 0000000000..981040af5a --- /dev/null +++ b/icons/collection.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/lightning.svg b/icons/lightning.svg new file mode 100644 index 0000000000..91b1ae92ca --- /dev/null +++ b/icons/lightning.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/shared/monaco/icons/file.svg b/icons/monaco/file.svg similarity index 100% rename from ui/shared/monaco/icons/file.svg rename to icons/monaco/file.svg diff --git a/ui/shared/monaco/icons/folder-open.svg b/icons/monaco/folder-open.svg similarity index 100% rename from ui/shared/monaco/icons/folder-open.svg rename to icons/monaco/folder-open.svg diff --git a/ui/shared/monaco/icons/folder.svg b/icons/monaco/folder.svg similarity index 100% rename from ui/shared/monaco/icons/folder.svg rename to icons/monaco/folder.svg diff --git a/ui/shared/monaco/icons/solidity.svg b/icons/monaco/solidity.svg similarity index 100% rename from ui/shared/monaco/icons/solidity.svg rename to icons/monaco/solidity.svg diff --git a/ui/shared/monaco/icons/vyper.svg b/icons/monaco/vyper.svg similarity index 100% rename from ui/shared/monaco/icons/vyper.svg rename to icons/monaco/vyper.svg diff --git a/icons/moon-with-star.svg b/icons/moon-with-star.svg new file mode 100644 index 0000000000..24c2085873 --- /dev/null +++ b/icons/moon-with-star.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/moon.svg b/icons/moon.svg index 8565569fbd..57c4ee6564 100644 --- a/icons/moon.svg +++ b/icons/moon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/profile.svg b/icons/profile.svg new file mode 100644 index 0000000000..177eea9650 --- /dev/null +++ b/icons/profile.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/score/score-not-ok.svg b/icons/score/score-not-ok.svg new file mode 100644 index 0000000000..9b4533f6ac --- /dev/null +++ b/icons/score/score-not-ok.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/score/score-ok.svg b/icons/score/score-ok.svg new file mode 100644 index 0000000000..dbe11836fd --- /dev/null +++ b/icons/score/score-ok.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/sun.svg b/icons/sun.svg index b49126f6df..f90bf90eab 100644 --- a/icons/sun.svg +++ b/icons/sun.svg @@ -1,3 +1,3 @@ - + diff --git a/icons/verify-contract.svg b/icons/verify-contract.svg new file mode 100644 index 0000000000..5457c4f160 --- /dev/null +++ b/icons/verify-contract.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/api/resources.ts b/lib/api/resources.ts index bf1fc70c56..60d224dad6 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -26,12 +26,15 @@ import type { AddressTokensFilter, AddressTokensResponse, AddressWithdrawalsResponse, + AddressNFTsResponse, + AddressCollectionsResponse, + AddressNFTTokensFilter, } from 'types/api/address'; import type { AddressesResponse } from 'types/api/addresses'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; 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 { IndexingStatus } from 'types/api/indexingStatus'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; @@ -51,12 +54,21 @@ import type { TokenInstance, TokenInstanceTransfersCount, TokenVerifiedInfo, + TokenInventoryFilters, } from 'types/api/token'; import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens'; 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 { TxStateChanges } from 'types/api/txStateChanges'; +import type { VerifiedContractsSorting } from 'types/api/verifiedContracts'; import type { VisualizedContract } from 'types/api/visualization'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches'; @@ -235,6 +247,10 @@ export const RESOURCES = { pathParams: [ 'hash' as const ], filterFields: [], }, + tx_interpretation: { + path: '/api/v2/transactions/:hash/summary', + pathParams: [ 'hash' as const ], + }, withdrawals: { path: '/api/v2/withdrawals', filterFields: [], @@ -305,6 +321,16 @@ export const RESOURCES = { pathParams: [ 'hash' 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: { path: '/api/v2/addresses/:hash/withdrawals', pathParams: [ 'hash' as const ], @@ -343,6 +369,10 @@ export const RESOURCES = { path: '/api/v2/smart-contracts/:hash/verification/via/:method', pathParams: [ 'hash' as const, 'method' as const ], }, + contract_solidityscan_report: { + path: '/api/v2/smart-contracts/:hash/solidityscan-report', + pathParams: [ 'hash' as const ], + }, verified_contracts: { path: '/api/v2/smart-contracts', @@ -380,7 +410,7 @@ export const RESOURCES = { token_inventory: { path: '/api/v2/tokens/:hash/instances', pathParams: [ 'hash' as const ], - filterFields: [], + filterFields: [ 'holder_address_hash' as const ], }, tokens: { path: '/api/v2/tokens', @@ -576,7 +606,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'addresses' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'search' | -'address_logs' | 'address_tokens' | +'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' | 'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'tokens_bridged' | 'token_instance_transfers' | 'token_instance_holders' | 'verified_contracts' | @@ -626,6 +656,7 @@ Q extends 'tx_logs' ? LogsResponseTx : Q extends 'tx_token_transfers' ? TokenTransferResponse : Q extends 'tx_raw_trace' ? RawTracesResponse : Q extends 'tx_state_changes' ? TxStateChanges : +Q extends 'tx_interpretation' ? TxInterpretationResponse : Q extends 'addresses' ? AddressesResponse : Q extends 'address' ? Address : Q extends 'address_counters' ? AddressCounters : @@ -638,6 +669,8 @@ Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse : Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart : Q extends 'address_logs' ? LogsResponseAddress : Q extends 'address_tokens' ? AddressTokensResponse : +Q extends 'address_nfts' ? AddressNFTsResponse : +Q extends 'address_collections' ? AddressCollectionsResponse : Q extends 'address_withdrawals' ? AddressWithdrawalsResponse : Q extends 'token' ? TokenInfo : Q extends 'token_verified_info' ? TokenVerifiedInfo : @@ -659,6 +692,7 @@ Q extends 'contract_methods_read' ? Array : Q extends 'contract_methods_read_proxy' ? Array : Q extends 'contract_methods_write' ? Array : Q extends 'contract_methods_write_proxy' ? Array : +Q extends 'contract_solidityscan_report' ? SolidityscanReport : Q extends 'verified_contracts' ? VerifiedContractsResponse : Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : Q extends 'visualize_sol2uml' ? VisualizedContract : @@ -690,7 +724,10 @@ Q extends 'token_transfers' ? TokenTransferFilters : Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : Q extends 'address_token_transfers' ? AddressTokenTransferFilters : Q extends 'address_tokens' ? AddressTokensFilter : +Q extends 'address_nfts' ? AddressNFTTokensFilter : +Q extends 'address_collections' ? AddressNFTTokensFilter : Q extends 'search' ? SearchResultFilters : +Q extends 'token_inventory' ? TokenInventoryFilters : Q extends 'tokens' ? TokensFilters : Q extends 'tokens_bridged' ? TokensBridgedFilters : Q extends 'verified_contracts' ? VerifiedContractsFilters : @@ -701,5 +738,7 @@ never; export type PaginationSorting = Q extends 'tokens' ? TokensSorting : Q extends 'tokens_bridged' ? TokensSorting : +Q extends 'verified_contracts' ? VerifiedContractsSorting : +Q extends 'address_txs' ? TransactionsSorting : never; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/cookies.ts b/lib/cookies.ts index 9634ebfc15..4e2d494102 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -9,9 +9,11 @@ export enum NAMES { CONFIRM_EMAIL_PAGE_VIEWED='confirm_email_page_viewed', TXS_SORT='txs_sort', COLOR_MODE='chakra-ui-color-mode', + COLOR_MODE_HEX='chakra-ui-color-mode-hex', INDEXING_ALERT='indexing_alert', ADBLOCK_DETECTED='adblock_detected', MIXPANEL_DEBUG='_mixpanel_debug', + ADDRESS_NFT_DISPLAY_TYPE='address_nft_display_type' } export function get(name?: NAMES | undefined | null, serverCookie?: string) { diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index 4a6e2552ae..f330a53c07 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -4,27 +4,6 @@ import React from 'react'; import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation-items'; 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 UserAvatar from 'ui/shared/UserAvatar'; @@ -49,35 +28,43 @@ export default function useNavItems(): ReturnType { return React.useMemo(() => { let blockchainNavItems: Array | Array> = []; - const topAccounts = !config.UI.views.address.hiddenViews?.top_accounts ? { + const topAccounts: NavItem | null = !config.UI.views.address.hiddenViews?.top_accounts ? { text: 'Top accounts', nextRoute: { pathname: '/accounts' as const }, - icon: topAccountsIcon, + icon: 'top-accounts', isActive: pathname === '/accounts', } : null; - const blocks = { + const blocks: NavItem | null = { text: 'Blocks', nextRoute: { pathname: '/blocks' as const }, - icon: blocksIcon, + icon: 'block', isActive: pathname === '/blocks' || pathname === '/block/[height_or_hash]', }; - const txs = { + const txs: NavItem | null = { text: 'Transactions', nextRoute: { pathname: '/txs' as const }, - icon: transactionsIcon, + icon: 'transactions', isActive: pathname === '/txs' || pathname === '/tx/[hash]', }; - const verifiedContracts = - // eslint-disable-next-line max-len - { text: 'Verified contracts', nextRoute: { pathname: '/verified-contracts' as const }, icon: verifiedIcon, isActive: pathname === '/verified-contracts' }; + const verifiedContracts: NavItem | null = + { + text: 'Verified contracts', + nextRoute: { pathname: '/verified-contracts' as const }, + icon: 'verified', + isActive: pathname === '/verified-contracts', + }; if (config.features.zkEvmRollup.isEnabled) { blockchainNavItems = [ [ txs, 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, @@ -89,16 +76,16 @@ export default function useNavItems(): ReturnType { [ txs, // 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 - { 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, // 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 - { 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, @@ -114,7 +101,7 @@ export default function useNavItems(): ReturnType { config.features.beaconChain.isEnabled && { text: 'Withdrawals', nextRoute: { pathname: '/withdrawals' as const }, - icon: withdrawalsIcon, + icon: 'arrows/north-east', isActive: pathname === '/withdrawals', }, ].filter(Boolean); @@ -124,23 +111,23 @@ export default function useNavItems(): ReturnType { config.features.restApiDocs.isEnabled ? { text: 'REST API', nextRoute: { pathname: '/api-docs' as const }, - icon: apiDocsIcon, + icon: 'restAPI', isActive: pathname === '/api-docs', } : null, config.features.graphqlApiDocs.isEnabled ? { text: 'GraphQL', nextRoute: { pathname: '/graphiql' as const }, - icon: graphQLIcon, + icon: 'graphQL', isActive: pathname === '/graphiql', } : null, !config.UI.sidebar.hiddenLinks?.rpc_api && { text: 'RPC API', - icon: rpcIcon, + icon: 'RPC', url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints', }, !config.UI.sidebar.hiddenLinks?.eth_rpc_api && { text: 'Eth RPC API', - icon: rpcIcon, + icon: 'RPC', url: ' https://docs.blockscout.com/for-users/api/eth-rpc', }, ].filter(Boolean); @@ -148,74 +135,83 @@ export default function useNavItems(): ReturnType { const mainNavItems: ReturnType['mainNavItems'] = [ { text: 'Blockchain', - icon: globeIcon, + icon: 'globe-b', isActive: blockchainNavItems.flat().some(item => isInternalItem(item) && item.isActive), subItems: blockchainNavItems, }, { text: 'Tokens', nextRoute: { pathname: '/tokens' as const }, - icon: tokensIcon, + icon: 'token', isActive: pathname.startsWith('/token'), }, config.features.marketplace.isEnabled ? { text: 'Apps', nextRoute: { pathname: '/apps' as const }, - icon: appsIcon, + icon: 'apps', isActive: pathname.startsWith('/app'), } : null, config.features.stats.isEnabled ? { text: 'Charts & stats', nextRoute: { pathname: '/stats' as const }, - icon: statsIcon, + icon: 'stats', isActive: pathname === '/stats', } : null, apiNavItems.length > 0 && { text: 'API', - icon: apiDocsIcon, + icon: 'restAPI', isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive), subItems: apiNavItems, }, - config.UI.sidebar.otherLinks.length > 0 ? { + { text: 'Other', - icon: gearIcon, - subItems: config.UI.sidebar.otherLinks, - } : null, + icon: 'gear', + subItems: [ + { + text: 'Verify contract', + nextRoute: { pathname: '/contract-verification' as const }, + isActive: pathname.startsWith('/contract-verification'), + }, + ...config.UI.sidebar.otherLinks, + ], + }, ].filter(Boolean); const accountNavItems: ReturnType['accountNavItems'] = [ { text: 'Watch list', nextRoute: { pathname: '/account/watchlist' as const }, - icon: watchlistIcon, + icon: 'watchlist', isActive: pathname === '/account/watchlist', }, { text: 'Private tags', nextRoute: { pathname: '/account/tag-address' as const }, - icon: privateTagIcon, + icon: 'privattags', isActive: pathname === '/account/tag-address', }, { text: 'Public tags', 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', nextRoute: { pathname: '/account/api-key' as const }, - icon: apiKeysIcon, isActive: pathname === '/account/api-key', + icon: 'API', + isActive: pathname === '/account/api-key', }, { text: 'Custom ABI', nextRoute: { pathname: '/account/custom-abi' as const }, - icon: abiIcon, + icon: 'ABI', isActive: pathname === '/account/custom-abi', }, config.features.addressVerification.isEnabled && { text: 'Verified addrs', nextRoute: { pathname: '/account/verified-addresses' as const }, - icon: verifiedIcon, + icon: 'verified', isActive: pathname === '/account/verified-addresses', }, ].filter(Boolean); diff --git a/lib/html-entities.ts b/lib/html-entities.ts index 6016ed4795..8df513b51d 100644 --- a/lib/html-entities.ts +++ b/lib/html-entities.ts @@ -1,5 +1,6 @@ -// https://unicode-table.com -export const asymp = String.fromCharCode(8776); // ~ +// https://symbl.cc/en/ +export const asymp = String.fromCharCode(8776); // ≈ +export const tilde = String.fromCharCode(126); // ~ export const hellip = String.fromCharCode(8230); // … export const nbsp = String.fromCharCode(160); // no-break Space export const thinsp = String.fromCharCode(8201); // thin Space diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index 5da92870b1..36e70e9fee 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -12,6 +12,7 @@ const OG_TYPE_DICT: Record = { '/accounts': 'Root page', '/address/[hash]': 'Regular page', '/verified-contracts': 'Root page', + '/contract-verification': 'Root page', '/address/[hash]/contract-verification': 'Regular page', '/tokens': 'Root page', '/token/[hash]': 'Regular page', diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index 25ced418dd..175e118f13 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -15,6 +15,7 @@ const TEMPLATE_MAP: Record = { '/accounts': DEFAULT_TEMPLATE, '/address/[hash]': 'View the account balance, transactions, and other data for %hash% on the %network_title%', '/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%', '/tokens': DEFAULT_TEMPLATE, '/token/[hash]': '%hash%, balances and analytics on the %network_title%', diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index a47e94cf5e..5a9e48eebb 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -10,6 +10,7 @@ const TEMPLATE_MAP: Record = { '/accounts': 'top accounts', '/address/[hash]': 'address details for %hash%', '/verified-contracts': 'verified contracts', + '/contract-verification': 'verify contract', '/address/[hash]/contract-verification': 'contract verification for %hash%', '/tokens': 'tokens', '/token/[hash]': '%symbol% token details', diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index dec0e8dc30..4ff5e22aa2 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -10,7 +10,8 @@ export const PAGE_TYPE_DICT: Record = { '/accounts': 'Top accounts', '/address/[hash]': 'Address details', '/verified-contracts': 'Verified contracts', - '/address/[hash]/contract-verification': 'Contract verification', + '/contract-verification': 'Contract verification', + '/address/[hash]/contract-verification': 'Contract verification for address', '/tokens': 'Tokens', '/token/[hash]': 'Token details', '/token/[hash]/instance/[id]': 'Token Instance', diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index f2dff64716..b886f32249 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -13,6 +13,7 @@ export enum EventTypes { CONTRACT_VERIFICATION = 'Contract verification', QR_CODE = 'QR code', PAGE_WIDGET = 'Page widget', + TX_INTERPRETATION_INTERACTION = 'Transaction interpratetion interaction' } /* eslint-disable @typescript-eslint/indent */ @@ -61,6 +62,7 @@ Type extends EventTypes.VERIFY_TOKEN ? { 'Action': 'Form opened' | 'Submit'; } : Type extends EventTypes.WALLET_CONNECT ? { + 'Source': 'Header' | 'Smart contracts'; 'Status': 'Started' | 'Connected'; } : Type extends EventTypes.CONTRACT_INTERACTION ? { @@ -77,5 +79,8 @@ Type extends EventTypes.QR_CODE ? { Type extends EventTypes.PAGE_WIDGET ? { '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; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/sentry/config.ts b/lib/sentry/config.ts index 9a6055d773..d52c20e382 100644 --- a/lib/sentry/config.ts +++ b/lib/sentry/config.ts @@ -1,4 +1,4 @@ -import type * as Sentry from '@sentry/react'; +import * as Sentry from '@sentry/react'; import { BrowserTracing } from '@sentry/tracing'; import appConfig from 'configs/app'; @@ -27,7 +27,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => { release: feature.release, enableTracing: feature.enableTracing, tracesSampleRate, - integrations: [ new BrowserTracing() ], + integrations: feature.enableTracing ? [ new BrowserTracing() ] : undefined, // error filtering settings // were taken from here - https://docs.sentry.io/platforms/node/guides/azure-functions/configuration/filtering/#decluttering-sentry @@ -56,6 +56,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => { 'Script error.', // Relay and WalletConnect errors + 'The quota has been exceeded', 'Attempt to connect to relay via', 'WebSocket connection failed for URL: wss://relay.walletconnect.com', ], @@ -67,9 +68,11 @@ export const config: Sentry.BrowserOptions | undefined = (() => { // Woopra flakiness /eatdifferent\.com\.woopra-ns\.com/i, /static\.woopra\.com\/js\/woopra\.js/i, - // Chrome extensions + // Chrome and other extensions /extensions\//i, /^chrome:\/\//i, + /^chrome-extension:\/\//i, + /^moz-extension:\/\//i, // Other plugins /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb /webappstoolbarba\.texthelp\.com\//i, @@ -90,3 +93,12 @@ export function configureScope(scope: Sentry.Scope) { } scope.setTag('app_instance', feature.instance); } + +export function init() { + if (!config) { + return; + } + + Sentry.init(config); + Sentry.configureScope(configureScope); +} diff --git a/lib/sentry/useConfigSentry.tsx b/lib/sentry/useConfigSentry.tsx deleted file mode 100644 index 70a37bb993..0000000000 --- a/lib/sentry/useConfigSentry.tsx +++ /dev/null @@ -1,16 +0,0 @@ -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); - }, []); -} diff --git a/lib/token/tokenTypes.ts b/lib/token/tokenTypes.ts index c8f258b4fb..5246fc2418 100644 --- a/lib/token/tokenTypes.ts +++ b/lib/token/tokenTypes.ts @@ -1,9 +1,14 @@ -import type { TokenType } from 'types/api/token'; +import type { NFTTokenType, TokenType } from 'types/api/token'; -export const TOKEN_TYPES: Array<{ title: string; id: TokenType }> = [ - { title: 'ERC-20', id: 'ERC-20' }, +export const NFT_TOKEN_TYPES: Array<{ title: string; id: NFTTokenType }> = [ { title: 'ERC-721', id: 'ERC-721' }, { 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); diff --git a/lib/tx/sortTxs.ts b/lib/tx/sortTxs.ts deleted file mode 100644 index 776638afe7..0000000000 --- a/lib/tx/sortTxs.ts +++ /dev/null @@ -1,21 +0,0 @@ -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; diff --git a/lib/web3/wallets.ts b/lib/web3/wallets.ts index 3100dc40a1..6bcb257e67 100644 --- a/lib/web3/wallets.ts +++ b/lib/web3/wallets.ts @@ -1,20 +1,16 @@ 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, WalletInfo> = { metamask: { name: 'MetaMask', - icon: metamaskIcon, + icon: 'wallets/metamask', }, coinbase: { name: 'Coinbase Wallet', - icon: coinbaseIcon, + icon: 'wallets/coinbase', }, token_pocket: { name: 'TokenPocket', - icon: tokenPocketIcon, + icon: 'wallets/token-pocket', }, }; diff --git a/mocks/address/tokens.ts b/mocks/address/tokens.ts index 5f2299cee5..c8c7386134 100644 --- a/mocks/address/tokens.ts +++ b/mocks/address/tokens.ts @@ -1,4 +1,4 @@ -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 tokenInstance from 'mocks/tokens/tokenInstance'; @@ -117,3 +117,51 @@ export const erc1155List = { 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', + }, +}; diff --git a/mocks/contract/methods.ts b/mocks/contract/methods.ts index 5f6f092d86..d9114114fa 100644 --- a/mocks/contract/methods.ts +++ b/mocks/contract/methods.ts @@ -14,7 +14,7 @@ export const read: Array = [ method_id: '70a08231', name: 'FLASHLOAN_PREMIUM_TOTAL', outputs: [ - { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, ], payable: false, stateMutability: 'view', @@ -97,7 +97,7 @@ export const read: Array = [ export const readResultSuccess: SmartContractQueryMethodReadSuccess = { is_error: false, result: { - names: [ 'uint256' ], + names: [ 'amount' ], output: [ { type: 'uint256', value: '42' }, ], diff --git a/mocks/contract/solidityscanReport.ts b/mocks/contract/solidityscanReport.ts new file mode 100644 index 0000000000..000adabf7f --- /dev/null +++ b/mocks/contract/solidityscanReport.ts @@ -0,0 +1,65 @@ +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', + }, +}; diff --git a/mocks/tokens/tokenInstance.ts b/mocks/tokens/tokenInstance.ts index b28361868f..4173c0a609 100644 --- a/mocks/tokens/tokenInstance.ts +++ b/mocks/tokens/tokenInstance.ts @@ -2,7 +2,6 @@ import type { TokenInstance } from 'types/api/token'; import * as addressMock from '../address/address'; -import { tokenInfoERC721a } from './tokenInfo'; export const base: TokenInstance = { animation_url: null, @@ -74,7 +73,6 @@ export const base: TokenInstance = { name: 'GENESIS #188848, 22a5f8bbb1602995. Blockchain pixel PFP NFT + "on music video" trait inspired by God', }, owner: addressMock.withName, - token: tokenInfoERC721a, }; export const withRichMetadata: TokenInstance = { diff --git a/mocks/txs/txInterpretation.ts b/mocks/txs/txInterpretation.ts new file mode 100644 index 0000000000..b351363a52 --- /dev/null +++ b/mocks/txs/txInterpretation.ts @@ -0,0 +1,45 @@ +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', + }, + }, + } ], + }, +}; diff --git a/next.config.js b/next.config.js index 9c87b63887..8789a1a641 100644 --- a/next.config.js +++ b/next.config.js @@ -31,6 +31,7 @@ const moduleExports = { }, ); config.resolve.fallback = { fs: false, net: false, tls: false }; + config.externals.push('pino-pretty', 'lokijs', 'encoding'); return config; }, diff --git a/nextjs/PageNextJs.tsx b/nextjs/PageNextJs.tsx index feb86c3f3e..1c15c9f419 100644 --- a/nextjs/PageNextJs.tsx +++ b/nextjs/PageNextJs.tsx @@ -7,18 +7,19 @@ import useAdblockDetect from 'lib/hooks/useAdblockDetect'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import * as metadata from 'lib/metadata'; import * as mixpanel from 'lib/mixpanel'; -import useConfigSentry from 'lib/sentry/useConfigSentry'; +import { init as initSentry } from 'lib/sentry/config'; type Props = Route & { children: React.ReactNode; } +initSentry(); + const PageNextJs = (props: Props) => { const { title, description, opengraph } = metadata.generate(props); useGetCsrfToken(); useAdblockDetect(); - useConfigSentry(); const isMixpanelInited = mixpanel.useInit(); mixpanel.useLogPageView(isMixpanelInited); diff --git a/nextjs/csp/policies/ad.ts b/nextjs/csp/policies/ad.ts index 4b7905fdf2..2808dfb07c 100644 --- a/nextjs/csp/policies/ad.ts +++ b/nextjs/csp/policies/ad.ts @@ -9,11 +9,11 @@ export function ad(): CspDev.DirectiveDescriptor { 'connect-src': [ 'coinzilla.com', '*.coinzilla.com', - 'request-global.czilladx.com', + 'https://request-global.czilladx.com', '*.slise.xyz', ], 'frame-src': [ - 'request-global.czilladx.com', + 'https://request-global.czilladx.com', ], 'script-src': [ 'coinzillatag.com', @@ -27,7 +27,7 @@ export function ad(): CspDev.DirectiveDescriptor { 'cdn.coinzilla.io', ], 'font-src': [ - 'request-global.czilladx.com', + 'https://request-global.czilladx.com', ], }; } diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index ef338a22dc..37822e4766 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -9,7 +9,6 @@ import { KEY_WORDS } from '../utils'; const MAIN_DOMAINS = [ `*.${ config.app.host }`, config.app.host, - getFeaturePayload(config.features.sol2uml)?.api.endpoint, ].filter(Boolean); const getCspReportUrl = () => { @@ -113,6 +112,7 @@ export function app(): CspDev.DirectiveDescriptor { 'font-src': [ KEY_WORDS.DATA, + ...MAIN_DOMAINS, ], 'object-src': [ diff --git a/nextjs/csp/policies/walletConnect.ts b/nextjs/csp/policies/walletConnect.ts index 7dcff73b87..41cb948066 100644 --- a/nextjs/csp/policies/walletConnect.ts +++ b/nextjs/csp/policies/walletConnect.ts @@ -2,6 +2,8 @@ import type CspDev from 'csp-dev'; import config from 'configs/app'; +import { KEY_WORDS } from '../utils'; + export function walletConnect(): CspDev.DirectiveDescriptor { if (!config.features.blockchainInteraction.isEnabled) { return {}; @@ -9,11 +11,13 @@ export function walletConnect(): CspDev.DirectiveDescriptor { return { 'connect-src': [ + '*.web3modal.com', '*.walletconnect.com', 'wss://relay.walletconnect.com', 'wss://www.walletlink.org', ], 'img-src': [ + KEY_WORDS.BLOB, '*.walletconnect.com', ], }; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index a39e9e3884..aa7c203b56 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -28,6 +28,7 @@ declare module "nextjs-routes" { | StaticRoute<"/auth/unverified-email"> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> | StaticRoute<"/blocks"> + | StaticRoute<"/contract-verification"> | StaticRoute<"/csv-export"> | StaticRoute<"/graphiql"> | StaticRoute<"/"> diff --git a/package.json b/package.json index 918c4419f3..395abadc60 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "npm": "8" }, "scripts": { - "dev": "next dev", + "dev": "./tools/scripts/dev.sh", "dev:preset": "./tools/scripts/dev.preset.sh", "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 ./", @@ -20,7 +20,8 @@ "lint:tsc": "tsc -p ./tsconfig.json", "lint:envs-validator:test": "cd ./deploy/tools/envs-validator && ./test.sh", "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: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", @@ -53,19 +54,19 @@ "@tanstack/react-query-devtools": "^5.4.3", "@types/papaparse": "^5.3.5", "@types/react-scroll": "^1.8.4", - "@web3modal/ethereum": "^2.6.2", - "@web3modal/react": "^2.6.2", + "@web3modal/wagmi": "3.5.0", "bignumber.js": "^9.1.0", "blo": "^1.1.1", "chakra-react-select": "^4.4.3", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "d3": "^7.6.1", + "dappscout-iframe": "^0.1.0", "dayjs": "^1.11.5", "dom-to-image": "^2.6.0", "framer-motion": "^6.5.1", "gradient-avatar": "^1.0.2", "graphiql": "^2.2.0", - "graphql": "^16.6.0", + "graphql": "^16.8.1", "graphql-ws": "^5.11.3", "js-cookie": "^3.0.1", "lodash": "^4.0.0", @@ -91,8 +92,8 @@ "react-scroll": "^1.8.7", "swagger-ui-react": "^5.9.0", "use-font-face-observer": "^1.2.1", - "viem": "^1.1.8", - "wagmi": "^1.3.3", + "viem": "1.20.1", + "wagmi": "1.4.12", "xss": "^1.0.14" }, "devDependencies": { @@ -136,6 +137,7 @@ "lint-staged": ">=10", "mockdate": "^3.0.5", "style-loader": "^3.3.1", + "svg-icons-cli": "^0.0.5", "svgo": "^2.8.0", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", diff --git a/pages/_app.tsx b/pages/_app.tsx index b9d77d5c53..14a70d043f 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -17,6 +17,7 @@ import theme from 'theme'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import GoogleAnalytics from 'ui/shared/GoogleAnalytics'; import Layout from 'ui/shared/layout/Layout'; +import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import 'lib/setLocale'; @@ -52,17 +53,19 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { { ...ERROR_SCREEN_STYLES } onError={ handleError } > - - - - - { getLayout() } - - - - - - + + + + + + { getLayout() } + + + + + + + ); diff --git a/pages/_document.tsx b/pages/_document.tsx index 6d21b3532c..74ebe08fb5 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -6,6 +6,7 @@ import React from 'react'; import * as serverTiming from 'nextjs/utils/serverTiming'; import theme from 'theme'; +import * as svgSprite from 'ui/shared/IconSvg'; class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { @@ -48,6 +49,8 @@ class MyDocument extends Document { + + diff --git a/pages/address/[hash]/contract-verification.tsx b/pages/address/[hash]/contract-verification.tsx index cb579c771a..7e7fd704d2 100644 --- a/pages/address/[hash]/contract-verification.tsx +++ b/pages/address/[hash]/contract-verification.tsx @@ -4,12 +4,12 @@ import React from 'react'; import type { Props } from 'nextjs/getServerSideProps'; import PageNextJs from 'nextjs/PageNextJs'; -import ContractVerification from 'ui/pages/ContractVerification'; +import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddress'; const Page: NextPage = (props: Props) => { return ( - + ); }; diff --git a/pages/apps/[id].tsx b/pages/apps/[id].tsx index 108643409e..f99be74026 100644 --- a/pages/apps/[id].tsx +++ b/pages/apps/[id].tsx @@ -6,6 +6,8 @@ import type { NextPageWithLayout } from 'nextjs/types'; import type { Props } from 'nextjs/getServerSideProps'; import PageNextJs from 'nextjs/PageNextJs'; +import LayoutApp from 'ui/shared/layout/LayoutApp'; + const MarketplaceApp = dynamic(() => import('ui/pages/MarketplaceApp'), { ssr: false }); const Page: NextPageWithLayout = (props: Props) => { @@ -16,6 +18,14 @@ const Page: NextPageWithLayout = (props: Props) => { ); }; +Page.getLayout = function getLayout(page: React.ReactElement) { + return ( + + { page } + + ); +}; + export default Page; export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/apps/index.tsx b/pages/apps/index.tsx index 7184417759..17f75586ac 100644 --- a/pages/apps/index.tsx +++ b/pages/apps/index.tsx @@ -4,15 +4,26 @@ import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; +import config from 'configs/app'; +import LinkExternal from 'ui/shared/LinkExternal'; import PageTitle from 'ui/shared/Page/PageTitle'; +const feature = config.features.marketplace; + const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false }); const Page: NextPage = () => { return ( <> - + + Submit app + + ) } + /> diff --git a/pages/contract-verification.tsx b/pages/contract-verification.tsx new file mode 100644 index 0000000000..a14df711e1 --- /dev/null +++ b/pages/contract-verification.tsx @@ -0,0 +1,19 @@ +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) => { + return ( + + + + ); +}; + +export default Page; + +export { base as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright/TestApp.tsx b/playwright/TestApp.tsx index fc644a9ad8..748e52c052 100644 --- a/playwright/TestApp.tsx +++ b/playwright/TestApp.tsx @@ -1,8 +1,8 @@ import { ChakraProvider } from '@chakra-ui/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { w3mProvider } from '@web3modal/ethereum'; +import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'; import React from 'react'; -import { configureChains, createConfig, WagmiConfig } from 'wagmi'; +import { WagmiConfig } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import type { Props as PageProps } from 'nextjs/getServerSideProps'; @@ -33,17 +33,18 @@ const defaultAppContext = { }; // >>> Web3 stuff -const { publicClient } = configureChains( - [ mainnet ], - [ - w3mProvider({ projectId: '' }), - ], -); +const chains = [ mainnet ]; +const WALLET_CONNECT_PROJECT_ID = 'PROJECT_ID'; -const wagmiConfig = createConfig({ - autoConnect: false, - connectors: [ ], - publicClient, +const wagmiConfig = defaultWagmiConfig({ + chains, + projectId: WALLET_CONNECT_PROJECT_ID, +}); + +createWeb3Modal({ + wagmiConfig, + projectId: WALLET_CONNECT_PROJECT_ID, + chains, }); // <<<< diff --git a/playwright/index.html b/playwright/index.html index 8e124e4903..f902263094 100644 --- a/playwright/index.html +++ b/playwright/index.html @@ -9,5 +9,6 @@
+ diff --git a/playwright/utils/configs.ts b/playwright/utils/configs.ts index 5871ee3c45..4d9bc8802f 100644 --- a/playwright/utils/configs.ts +++ b/playwright/utils/configs.ts @@ -29,6 +29,9 @@ export const featureEnvs = { 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: [ { name: 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK', value: 'true' }, { name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts new file mode 100644 index 0000000000..31e3f50b9d --- /dev/null +++ b/public/icons/name.d.ts @@ -0,0 +1,133 @@ +// 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 diff --git a/stubs/address.ts b/stubs/address.ts index f4c3ec2f52..c2e0ffd7c6 100644 --- a/stubs/address.ts +++ b/stubs/address.ts @@ -1,4 +1,12 @@ -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 { ADDRESS_HASH } from './addressParams'; @@ -80,16 +88,22 @@ export const ADDRESS_TOKEN_BALANCE_ERC_20: AddressTokenBalance = { 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_id: null, - token_instance: null, - value: '176', + value: '1', + ...TOKEN_INSTANCE, +}; + +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_id: '188882', - token_instance: TOKEN_INSTANCE, - value: '176', + amount: '4', + token_instances: Array(4).fill(TOKEN_INSTANCE), }; diff --git a/stubs/contract.ts b/stubs/contract.ts index 12bba6c130..36d7b13ed2 100644 --- a/stubs/contract.ts +++ b/stubs/contract.ts @@ -1,4 +1,4 @@ -import type { SmartContract } from 'types/api/contract'; +import type { SmartContract, SolidityscanReport } from 'types/api/contract'; import type { VerifiedContract } from 'types/api/contracts'; import { ADDRESS_PARAMS } from './addressParams'; @@ -53,3 +53,25 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = { tx_count: 565058, 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', + }, +}; diff --git a/stubs/token.ts b/stubs/token.ts index 30b8b487bf..e60e004bad 100644 --- a/stubs/token.ts +++ b/stubs/token.ts @@ -108,6 +108,5 @@ export const TOKEN_INSTANCE: TokenInstance = { name: 'GENESIS #188882, 8a77ca1bcaa4036f. Blockchain pixel PFP NFT + "on music video" trait inspired by God', }, owner: ADDRESS_PARAMS, - token: TOKEN_INFO_ERC_1155, holder_address_hash: ADDRESS_HASH, }; diff --git a/stubs/txInterpretation.ts b/stubs/txInterpretation.ts new file mode 100644 index 0000000000..e54c2cddae --- /dev/null +++ b/stubs/txInterpretation.ts @@ -0,0 +1,34 @@ +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, + }, + }, + }, + ], + }, +}; diff --git a/svgo.config.js b/svgo.config.js index 69994b1608..1c3d7c355d 100644 --- a/svgo.config.js +++ b/svgo.config.js @@ -5,11 +5,11 @@ module.exports = { params: { overrides: { removeViewBox: false, + removeHiddenElems: false, }, }, }, 'removeDimensions', - 'prefixIds', ], js2svg: { indent: 2, diff --git a/tools/scripts/dev.preset.sh b/tools/scripts/dev.preset.sh index 1110089f72..c8566819e0 100755 --- a/tools/scripts/dev.preset.sh +++ b/tools/scripts/dev.preset.sh @@ -14,16 +14,14 @@ if [ ! -f "$config_file" ]; then exit 1 fi -if [ ! -f "$secrets_file" ]; then - echo "Error: File '$secrets_file' not found." - exit 1 -fi - # download assets for the running instance dotenv \ -e $config_file \ -- 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) \ diff --git a/tools/scripts/dev.sh b/tools/scripts/dev.sh new file mode 100755 index 0000000000..fabcad2ff4 --- /dev/null +++ b/tools/scripts/dev.sh @@ -0,0 +1,24 @@ +#!/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 diff --git a/tools/scripts/pw.sh b/tools/scripts/pw.sh index d99eebca94..22aee344a2 100755 --- a/tools/scripts/pw.sh +++ b/tools/scripts/pw.sh @@ -8,6 +8,8 @@ dotenv \ -e $config_file \ -- bash -c './deploy/scripts/make_envs_script.sh ./playwright/envs.js' +yarn svg:build-sprite + dotenv \ -v NODE_OPTIONS=\"--max-old-space-size=4096\" \ -e $config_file \ diff --git a/types/api/address.ts b/types/api/address.ts index 10dc6aa757..7edd55659e 100644 --- a/types/api/address.ts +++ b/types/api/address.ts @@ -3,7 +3,7 @@ import type { Transaction } from 'types/api/transaction'; import type { UserTags } from './addressParams'; import type { Block } from './block'; 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'; export interface Address extends UserTags { @@ -49,17 +49,47 @@ export interface AddressTokenBalance { token_instance: TokenInstance | null; } +export type AddressNFT = TokenInstance & { + token: TokenInfo; + token_type: Omit; + value: string; +} + +export type AddressCollection = { + token: TokenInfo; + amount: string; + token_instances: Array>; +} + export interface AddressTokensResponse { items: Array; next_page_params: { items_count: number; - token_name: 'string' | null; + token_name: string | null; token_type: TokenType; value: number; fiat_value: string | null; } | null; } +export interface AddressNFTsResponse { + items: Array; + next_page_params: { + items_count: number; + token_id: string; + token_type: TokenType; + token_contract_address_hash: string; + } | null; +} + +export interface AddressCollectionsResponse { + items: Array; + next_page_params: { + token_contract_address_hash: string; + token_type: TokenType; + } | null; +} + export interface AddressTokensBalancesSocketMessage { overflow: boolean; token_balances: Array; @@ -97,6 +127,10 @@ export type AddressTokensFilter = { type: TokenType; } +export type AddressNFTTokensFilter = { + type: Array | undefined; +} + export interface AddressCoinBalanceHistoryItem { block_number: number; block_timestamp: string; diff --git a/types/api/contract.ts b/types/api/contract.ts index d8ff3a0b3c..7c11167b1a 100644 --- a/types/api/contract.ts +++ b/types/api/contract.ts @@ -97,10 +97,10 @@ export interface SmartContractMethodOutput extends SmartContractMethodInput { export interface SmartContractQueryMethodReadSuccess { is_error: false; result: { - names: Array; + names: Array ]>; output: Array<{ type: string; - value: string; + value: string | Array; }>; }; } @@ -156,3 +156,25 @@ export interface SmartContractVerificationError { constructor_arguments?: Array; name?: Array; } + +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; + }; +} diff --git a/types/api/fee.ts b/types/api/fee.ts index aafda5a6ba..82ad5c5b8b 100644 --- a/types/api/fee.ts +++ b/types/api/fee.ts @@ -1,4 +1,4 @@ export interface Fee { type: string; - value: string; + value: string | null; } diff --git a/types/api/stats.ts b/types/api/stats.ts index b2d74eb508..7ce179368f 100644 --- a/types/api/stats.ts +++ b/types/api/stats.ts @@ -3,7 +3,7 @@ export type HomeStats = { total_addresses: string; total_transactions: string; average_block_time: number; - coin_price: string; + coin_price: string | null; total_gas_used: string; transactions_today: string; gas_used_today: string; @@ -16,9 +16,9 @@ export type HomeStats = { } export type GasPrices = { - average: number; - fast: number; - slow: number; + average: number | null; + fast: number | null; + slow: number | null; } export type Counters = { diff --git a/types/api/token.ts b/types/api/token.ts index ebc5198d92..417c74aab6 100644 --- a/types/api/token.ts +++ b/types/api/token.ts @@ -1,7 +1,8 @@ import type { TokenInfoApplication } from './account'; 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 { address: string; @@ -61,7 +62,6 @@ export interface TokenInstance { external_app_url: string | null; metadata: Record | null; owner: AddressParam | null; - token: TokenInfo; } export interface TokenInstanceTransfersCount { @@ -78,3 +78,7 @@ export type TokenInventoryPagination = { } export type TokenVerifiedInfo = Omit; + +export type TokenInventoryFilters = { + holder_address_hash?: string; +} diff --git a/types/api/transaction.ts b/types/api/transaction.ts index c323c16008..5035ce53a3 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -119,3 +119,12 @@ export type TransactionType = 'rootstock_remasc' | 'coin_transfer' 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'] }`; diff --git a/types/api/txInterpretation.ts b/types/api/txInterpretation.ts new file mode 100644 index 0000000000..be9393203f --- /dev/null +++ b/types/api/txInterpretation.ts @@ -0,0 +1,47 @@ +import type { AddressParam } from 'types/api/addressParams'; +import type { TokenInfo } from 'types/api/token'; + +export interface TxInterpretationResponse { + data: { + summaries: Array; + }; +} + +export type TxInterpretationSummary = { + summary_template: string; + summary_template_variables: Record; +} + +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; +} diff --git a/types/api/verifiedContracts.ts b/types/api/verifiedContracts.ts new file mode 100644 index 0000000000..12d45f31fc --- /dev/null +++ b/types/api/verifiedContracts.ts @@ -0,0 +1,8 @@ +export interface VerifiedContractsSorting { + sort: 'balance' | 'txs_count'; + order: 'asc' | 'desc'; +} + +export type VerifiedContractsSortingField = VerifiedContractsSorting['sort']; + +export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }`; diff --git a/types/client/navigation-items.ts b/types/client/navigation-items.ts index 6393a1e957..862b1cec84 100644 --- a/types/client/navigation-items.ts +++ b/types/client/navigation-items.ts @@ -2,8 +2,10 @@ import type React from 'react'; import type { Route } from 'nextjs-routes'; +import type { IconName } from 'ui/shared/IconSvg'; + type NavIconOrComponent = { - icon?: React.FunctionComponent>; + icon?: IconName; } | { iconComponent?: React.FC<{size?: number}>; }; diff --git a/types/client/txInterpretation.ts b/types/client/txInterpretation.ts new file mode 100644 index 0000000000..e264b267bc --- /dev/null +++ b/types/client/txInterpretation.ts @@ -0,0 +1,8 @@ +import type { ArrayElement } from 'types/utils'; + +export const PROVIDERS = [ + 'blockscout', + 'none', +] as const; + +export type Provider = ArrayElement; diff --git a/types/client/txs-sort.ts b/types/client/txs-sort.ts deleted file mode 100644 index 501b625a56..0000000000 --- a/types/client/txs-sort.ts +++ /dev/null @@ -1 +0,0 @@ -export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | ''; diff --git a/types/client/wallets.ts b/types/client/wallets.ts index 923963f5db..a112cd766f 100644 --- a/types/client/wallets.ts +++ b/types/client/wallets.ts @@ -1,5 +1,7 @@ import type { ArrayElement } from 'types/utils'; +import type { IconName } from 'ui/shared/IconSvg'; + export const SUPPORTED_WALLETS = [ 'metamask', 'coinbase', @@ -10,5 +12,5 @@ export type WalletType = ArrayElement; export interface WalletInfo { name: string; - icon: React.ElementType; + icon: IconName; } diff --git a/types/views/block.ts b/types/views/block.ts index 41d345927f..e924d6189b 100644 --- a/types/views/block.ts +++ b/types/views/block.ts @@ -4,6 +4,7 @@ export const BLOCK_FIELDS_IDS = [ 'burnt_fees', 'total_reward', 'nonce', + 'miner', ] as const; export type BlockFieldId = ArrayElement; diff --git a/ui/address/AddressCsvExportLink.tsx b/ui/address/AddressCsvExportLink.tsx index 0bbcfa7497..6ef1d2dd0a 100644 --- a/ui/address/AddressCsvExportLink.tsx +++ b/ui/address/AddressCsvExportLink.tsx @@ -1,4 +1,4 @@ -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 type { CsvExportParams } from 'types/client/address'; @@ -6,9 +6,9 @@ import type { CsvExportParams } from 'types/client/address'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import svgFileIcon from 'icons/files/csv.svg'; import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; import useIsMobile from 'lib/hooks/useIsMobile'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; interface Props { @@ -47,7 +47,7 @@ const AddressCsvExportLink = ({ className, address, params, isLoading }: Props) href={ route({ pathname: '/csv-export', query: { ...params, address } }) } flexShrink={ 0 } > - + Download CSV diff --git a/ui/address/AddressTokenTransfers.tsx b/ui/address/AddressTokenTransfers.tsx index 0355618015..0ebba17f8e 100644 --- a/ui/address/AddressTokenTransfers.tsx +++ b/ui/address/AddressTokenTransfers.tsx @@ -1,4 +1,4 @@ -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 { useRouter } from 'next/router'; import React from 'react'; @@ -9,7 +9,6 @@ import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/ap import type { TokenType } from 'types/api/token'; import type { TokenTransfer } from 'types/api/tokenTransfer'; -import crossIcon from 'icons/cross.svg'; import { getResourceKey } from 'lib/api/useApiQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; @@ -26,6 +25,7 @@ import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import HashStringShorten from 'ui/shared/HashStringShorten'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import ResetIconButton from 'ui/shared/ResetIconButton'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter'; import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList'; @@ -115,9 +115,6 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr onFilterChange({}); }, [ onFilterChange ]); - const resetTokenIconColor = useColorModeValue('blue.600', 'blue.300'); - const resetTokenIconHoverColor = useColorModeValue('blue.400', 'blue.200'); - const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => { setSocketAlert(''); @@ -235,19 +232,7 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr { isMobile ? : tokenFilter } - - - - - + ); diff --git a/ui/address/AddressTokens.pw.tsx b/ui/address/AddressTokens.pw.tsx index 3cf3b34e93..7ee5556986 100644 --- a/ui/address/AddressTokens.pw.tsx +++ b/ui/address/AddressTokens.pw.tsx @@ -13,6 +13,8 @@ import AddressTokens from './AddressTokens'; const ADDRESS_HASH = addressMock.withName.hash; const API_URL_ADDRESS = buildApiUrl('address', { 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 = { items_count: 50, @@ -52,6 +54,14 @@ const test = base.extend({ status: 200, 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); }, @@ -76,10 +86,10 @@ test('erc20 +@dark-mode', async({ mount }) => { await expect(component).toHaveScreenshot(); }); -test('erc721 +@dark-mode', async({ mount }) => { +test('collections +@dark-mode', async({ mount }) => { const hooksConfig = { router: { - query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' }, + query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' }, isReady: true, }, }; @@ -95,10 +105,10 @@ test('erc721 +@dark-mode', async({ mount }) => { await expect(component).toHaveScreenshot(); }); -test('erc1155 +@dark-mode', async({ mount }) => { +test('nfts +@dark-mode', async({ mount }) => { const hooksConfig = { router: { - query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' }, + query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' }, isReady: true, }, }; @@ -111,6 +121,8 @@ test('erc1155 +@dark-mode', async({ mount }) => { { hooksConfig }, ); + await component.getByText('List').click(); + await expect(component).toHaveScreenshot(); }); @@ -136,10 +148,10 @@ test.describe('mobile', () => { await expect(component).toHaveScreenshot(); }); - test('erc721', async({ mount }) => { + test('nfts', async({ mount }) => { const hooksConfig = { router: { - query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' }, + query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' }, isReady: true, }, }; @@ -152,13 +164,15 @@ test.describe('mobile', () => { { hooksConfig }, ); + await component.getByLabel('list').click(); + await expect(component).toHaveScreenshot(); }); - test('erc1155', async({ mount }) => { + test('collections', async({ mount }) => { const hooksConfig = { router: { - query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' }, + query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' }, isReady: true, }, }; diff --git a/ui/address/AddressTokens.tsx b/ui/address/AddressTokens.tsx index 1d693bd4d6..0e27a73f7d 100644 --- a/ui/address/AddressTokens.tsx +++ b/ui/address/AddressTokens.tsx @@ -1,47 +1,44 @@ -import { Box } from '@chakra-ui/react'; -import { useQueryClient } from '@tanstack/react-query'; +import { Box, HStack } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; -import type { SocketMessage } from 'lib/socket/types'; -import type { AddressTokenBalance, AddressTokensBalancesSocketMessage, AddressTokensResponse } from 'types/api/address'; -import type { TokenType } from 'types/api/token'; +import type { NFTTokenType } from 'types/api/token'; import type { PaginationParams } from 'ui/shared/pagination/types'; -import { getResourceKey } from 'lib/api/useApiQuery'; +import { useAppContext } from 'lib/contexts/app'; +import * as cookies from 'lib/cookies'; +import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; import useIsMobile from 'lib/hooks/useIsMobile'; import getQueryParamString from 'lib/router/getQueryParamString'; -import useSocketChannel from 'lib/socket/useSocketChannel'; -import useSocketMessage from 'lib/socket/useSocketMessage'; -import { ADDRESS_TOKEN_BALANCE_ERC_1155, ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_TOKEN_BALANCE_ERC_721 } from 'stubs/address'; +import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; +import { ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_NFT_1155, ADDRESS_COLLECTION } from 'stubs/address'; import { generateListStub } from 'stubs/utils'; -import { tokenTabsByType } from 'ui/pages/Address'; +import PopoverFilter from 'ui/shared/filters/PopoverFilter'; +import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; -import ERC1155Tokens from './tokens/ERC1155Tokens'; +import AddressCollections from './tokens/AddressCollections'; +import AddressNFTs from './tokens/AddressNFTs'; import ERC20Tokens from './tokens/ERC20Tokens'; -import ERC721Tokens from './tokens/ERC721Tokens'; import TokenBalances from './tokens/TokenBalances'; +type TNftDisplayType = 'collection' | 'list'; + const TAB_LIST_PROPS = { - marginBottom: 0, + my: 3, py: 5, - marginTop: 3, columnGap: 3, }; const TAB_LIST_PROPS_MOBILE = { - mt: 8, + my: 8, columnGap: 3, }; -const tokenBalanceItemIdentityFactory = (match: AddressTokenBalance) => (item: AddressTokenBalance) => (( - match.token.address === item.token.address && - match.token_id === item.token_id && - match.token_instance?.id === item.token_instance?.id -)); +const getTokenFilterValue = (getFilterValuesFromQuery).bind(null, NFT_TOKEN_TYPE_IDS); const AddressTokens = () => { const router = useRouter(); @@ -49,6 +46,10 @@ const AddressTokens = () => { const scrollRef = React.useRef(null); + const displayTypeCookie = cookies.get(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, useAppContext().cookies); + const [ nftDisplayType, setNftDisplayType ] = React.useState(displayTypeCookie === 'list' ? 'list' : 'collection'); + const [ tokenTypes, setTokenTypes ] = React.useState | undefined>(getTokenFilterValue(router.query.type) || []); + const tab = getQueryParamString(router.query.tab); const hash = getQueryParamString(router.query.hash); @@ -58,111 +59,100 @@ const AddressTokens = () => { filters: { type: 'ERC-20' }, scrollRef, options: { + enabled: !tab || tab === 'tokens_erc20', refetchOnMount: false, placeholderData: generateListStub<'address_tokens'>(ADDRESS_TOKEN_BALANCE_ERC_20, 10, { next_page_params: null }), }, }); - const erc721Query = useQueryWithPages({ - resourceName: 'address_tokens', + const collectionsQuery = useQueryWithPages({ + resourceName: 'address_collections', pathParams: { hash }, - filters: { type: 'ERC-721' }, scrollRef, options: { - refetchOnMount: false, - placeholderData: generateListStub<'address_tokens'>(ADDRESS_TOKEN_BALANCE_ERC_721, 10, { next_page_params: null }), + enabled: tab === 'tokens_nfts' && nftDisplayType === 'collection', + placeholderData: generateListStub<'address_collections'>(ADDRESS_COLLECTION, 10, { next_page_params: null }), }, + filters: { type: tokenTypes }, }); - const erc1155Query = useQueryWithPages({ - resourceName: 'address_tokens', + const nftsQuery = useQueryWithPages({ + resourceName: 'address_nfts', pathParams: { hash }, - filters: { type: 'ERC-1155' }, scrollRef, options: { - refetchOnMount: false, - placeholderData: generateListStub<'address_tokens'>(ADDRESS_TOKEN_BALANCE_ERC_1155, 10, { next_page_params: null }), + enabled: tab === 'tokens_nfts' && nftDisplayType === 'list', + placeholderData: generateListStub<'address_nfts'>(ADDRESS_NFT_1155, 10, { next_page_params: null }), }, + filters: { type: tokenTypes }, }); - const queryClient = useQueryClient(); - - const updateTokensData = React.useCallback((type: TokenType, payload: AddressTokensBalancesSocketMessage) => { - const queryKey = getResourceKey('address_tokens', { pathParams: { hash }, queryParams: { type } }); - - queryClient.setQueryData(queryKey, (prevData: AddressTokensResponse | undefined) => { - const items = prevData?.items.map((currentItem) => { - const updatedData = payload.token_balances.find(tokenBalanceItemIdentityFactory(currentItem)); - return updatedData ?? currentItem; - }) || []; - - const extraItems = prevData?.next_page_params ? - [] : - payload.token_balances.filter((socketItem) => !items.some(tokenBalanceItemIdentityFactory(socketItem))); - - if (!prevData) { - return { - items: extraItems, - next_page_params: null, - }; - } - - return { - items: items.concat(extraItems), - next_page_params: prevData.next_page_params, - }; - }); - }, [ hash, queryClient ]); - - const handleTokenBalancesErc20Message: SocketMessage.AddressTokenBalancesErc20['handler'] = React.useCallback((payload) => { - updateTokensData('ERC-20', payload); - }, [ updateTokensData ]); - - const handleTokenBalancesErc721Message: SocketMessage.AddressTokenBalancesErc721['handler'] = React.useCallback((payload) => { - updateTokensData('ERC-721', payload); - }, [ updateTokensData ]); - - const handleTokenBalancesErc1155Message: SocketMessage.AddressTokenBalancesErc1155['handler'] = React.useCallback((payload) => { - updateTokensData('ERC-1155', payload); - }, [ updateTokensData ]); - - const channel = useSocketChannel({ - topic: `addresses:${ hash.toLowerCase() }`, - isDisabled: erc20Query.isPlaceholderData || erc721Query.isPlaceholderData || erc1155Query.isPlaceholderData, - }); + const handleNFTsDisplayTypeChange = React.useCallback((val: TNftDisplayType) => { + cookies.set(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, val); + setNftDisplayType(val); + }, []); + + const handleTokenTypesChange = React.useCallback((value: Array) => { + nftsQuery.onFilterChange({ type: value }); + collectionsQuery.onFilterChange({ type: value }); + setTokenTypes(value); + }, [ nftsQuery, collectionsQuery ]); + + const nftTypeFilter = ( + 0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }> + nftOnly onChange={ handleTokenTypesChange } defaultValue={ tokenTypes }/> + + ); - useSocketMessage({ - channel, - event: 'updated_token_balances_erc_20', - handler: handleTokenBalancesErc20Message, - }); - useSocketMessage({ - channel, - event: 'updated_token_balances_erc_721', - handler: handleTokenBalancesErc721Message, - }); - useSocketMessage({ - channel, - event: 'updated_token_balances_erc_1155', - handler: handleTokenBalancesErc1155Message, - }); + const hasActiveFilters = Boolean(tokenTypes?.length); const tabs = [ - { id: tokenTabsByType['ERC-20'], title: 'ERC-20', component: }, - { id: tokenTabsByType['ERC-721'], title: 'ERC-721', component: }, - { id: tokenTabsByType['ERC-1155'], title: 'ERC-1155', component: }, + { id: 'tokens_erc20', title: 'ERC-20', component: }, + { + id: 'tokens_nfts', + title: 'NFTs', + component: nftDisplayType === 'list' ? + : + , + }, ]; + const nftDisplayTypeRadio = ( + + onChange={ handleNFTsDisplayTypeChange } + defaultValue={ nftDisplayType } + name="type" + options={ [ + { title: 'By collection', value: 'collection', icon: 'collection', onlyIcon: isMobile }, + { title: 'List', value: 'list', icon: 'apps', onlyIcon: isMobile }, + ] } + /> + ); + let pagination: PaginationParams | undefined; - if (tab === tokenTabsByType['ERC-1155']) { - pagination = erc1155Query.pagination; - } else if (tab === tokenTabsByType['ERC-721']) { - pagination = erc721Query.pagination; + if (tab === 'tokens_nfts') { + pagination = nftDisplayType === 'list' ? nftsQuery.pagination : collectionsQuery.pagination; } else { pagination = erc20Query.pagination; } + const hasNftData = + (!nftsQuery.isPlaceholderData && nftsQuery.data?.items.length) || + (!collectionsQuery.isPlaceholderData && collectionsQuery.data?.items.length); + + const isNftTab = tab !== 'tokens' && tab !== 'tokens_erc20'; + + const rightSlot = ( + <> + + { isNftTab && (hasNftData || hasActiveFilters) && nftDisplayTypeRadio } + { isNftTab && (hasNftData || hasActiveFilters) && nftTypeFilter } + + { pagination.isVisible && !isMobile && } + + ); + return ( <> @@ -174,7 +164,8 @@ const AddressTokens = () => { colorScheme="gray" size="sm" tabListProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS } - rightSlot={ pagination.isVisible && !isMobile ? : null } + rightSlot={ rightSlot } + rightSlotProps={ tab !== 'tokens_erc20' && !isMobile ? { flexGrow: 1, display: 'flex', justifyContent: 'space-between', ml: 8 } : {} } stickyEnabled={ !isMobile } /> diff --git a/ui/address/AddressTxs.tsx b/ui/address/AddressTxs.tsx index 3dcbec39f9..cc21c355df 100644 --- a/ui/address/AddressTxs.tsx +++ b/ui/address/AddressTxs.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { SocketMessage } from 'lib/socket/types'; import type { AddressFromToFilter, AddressTransactionsResponse } 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 getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; @@ -18,7 +18,10 @@ import { generateListStub } from 'stubs/utils'; import ActionBar from 'ui/shared/ActionBar'; import Pagination from 'ui/shared/pagination/Pagination'; 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 AddressTxsFilter from './AddressTxsFilter'; @@ -53,6 +56,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { const [ socketAlert, setSocketAlert ] = React.useState(''); const [ newItemsCount, setNewItemsCount ] = React.useState(0); + const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS)); const isMobile = useIsMobile(); const currentAddress = getQueryParamString(router.query.hash); @@ -63,6 +67,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { resourceName: 'address_txs', pathParams: { hash: currentAddress }, filters: { filter: filterValue }, + sorting: getSortParamsFromValue(sort), scrollRef, options: { placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: { @@ -177,7 +182,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ) } - { socketInfoAlert={ socketAlert } socketInfoNum={ newItemsCount } top={ 80 } + sorting={ sort } + setSort={ setSort } /> ); diff --git a/ui/address/SolidityscanReport.pw.tsx b/ui/address/SolidityscanReport.pw.tsx new file mode 100644 index 0000000000..fd41c7a860 --- /dev/null +++ b/ui/address/SolidityscanReport.pw.tsx @@ -0,0 +1,68 @@ +import { test, expect } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import * as solidityscanReportMock from 'mocks/contract/solidityscanReport'; +import TestApp from 'playwright/TestApp'; +import buildApiUrl from 'playwright/utils/buildApiUrl'; + +import SolidityscanReport from './SolidityscanReport'; + +const addressHash = 'hash'; +const REPORT_API_URL = buildApiUrl('contract_solidityscan_report', { hash: addressHash }); + +test('average report +@dark-mode +@mobile', async({ mount, page }) => { + await page.route(REPORT_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(solidityscanReportMock.solidityscanReportAverage), + })); + + const component = await mount( + + + , + ); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 100, height: 50 } }); + + await component.getByLabel('SolidityScan score').click(); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 400, height: 500 } }); +}); + +test('great report', async({ mount, page }) => { + await page.route(REPORT_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(solidityscanReportMock.solidityscanReportGreat), + })); + + const component = await mount( + + + , + ); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 100, height: 50 } }); + + await component.getByLabel('SolidityScan score').click(); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 400, height: 500 } }); +}); + +test('low report', async({ mount, page }) => { + await page.route(REPORT_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(solidityscanReportMock.solidityscanReportLow), + })); + + const component = await mount( + + + , + ); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 100, height: 50 } }); + + await component.getByLabel('SolidityScan score').click(); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 400, height: 500 } }); +}); diff --git a/ui/address/SolidityscanReport.tsx b/ui/address/SolidityscanReport.tsx new file mode 100644 index 0000000000..8fe817d4fd --- /dev/null +++ b/ui/address/SolidityscanReport.tsx @@ -0,0 +1,175 @@ +import { + Box, + Flex, + Text, + Grid, + Button, + chakra, + Popover, + PopoverTrigger, + PopoverBody, + PopoverContent, + useDisclosure, + Skeleton, + Center, + useColorModeValue, +} from '@chakra-ui/react'; +import React from 'react'; + +import { SolidityscanReport } from 'types/api/contract'; + +import useApiQuery from 'lib/api/useApiQuery'; +import { SOLIDITYSCAN_REPORT } from 'stubs/contract'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkExternal from 'ui/shared/LinkExternal'; + +type DistributionItem = { + id: keyof SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + name: string; + color: string; +} + +const DISTRIBUTION_ITEMS: Array = [ + { id: 'critical', name: 'Critical', color: '#891F11' }, + { id: 'high', name: 'High', color: '#EC672C' }, + { id: 'medium', name: 'Medium', color: '#FBE74D' }, + { id: 'low', name: 'Low', color: '#68C88E' }, + { id: 'informational', name: 'Informational', color: '#A3AEBE' }, + { id: 'gas', name: 'Gas', color: '#A47585' }, +]; + +interface Props { + className?: string; + hash: string; +} + +type ItemProps = { + item: DistributionItem; + vulnerabilities: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + vulnerabilitiesCount: number; +} + +const SolidityScanReportItem = ({ item, vulnerabilities, vulnerabilitiesCount }: ItemProps) => { + const bgBar = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); + const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); + + return ( + <> + + + { item.name } + 0 ? 'text' : yetAnotherGrayColor }>{ vulnerabilities[item.id] } + + + + + + ); +}; + +const SolidityscanReport = ({ className, hash }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const { data, isPlaceholderData, isError } = useApiQuery('contract_solidityscan_report', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash), + placeholderData: SOLIDITYSCAN_REPORT, + }, + }); + + const score = Number(data?.scan_report.scan_summary.score_v2); + + const chartGrayColor = useColorModeValue('gray.100', 'gray.700'); + const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); + const popoverBgColor = useColorModeValue('white', 'gray.900'); + + const greatScoreColor = useColorModeValue('green.600', 'green.400'); + const averageScoreColor = useColorModeValue('purple.600', 'purple.400'); + const lowScoreColor = useColorModeValue('red.600', 'red.400'); + + if (isError || !score) { + return null; + } + + let scoreColor; + let scoreLevel; + if (score >= 80) { + scoreColor = greatScoreColor; + scoreLevel = 'GREAT'; + } else if (score >= 30) { + scoreColor = averageScoreColor; + scoreLevel = 'AVERAGE'; + } else { + scoreColor = lowScoreColor; + scoreLevel = 'LOW'; + } + + const vulnerabilities = data?.scan_report.scan_summary.issue_severity_distribution; + const vulnerabilitiesCounts = vulnerabilities ? Object.values(vulnerabilities) : []; + const vulnerabilitiesCount = vulnerabilitiesCounts.reduce((acc, val) => acc + val, 0); + + return ( + + + + + + + + + Contract analyzed for 140+ vulnerability patterns by SolidityScan + + +
+ +
+
+ + + { score } + / 100 + + Security score is { scoreLevel } + +
+ { vulnerabilities && vulnerabilitiesCount > 0 && ( + + Vulnerabilities distribution + + { DISTRIBUTION_ITEMS.map(item => ( + + )) } + + + ) } + View full report +
+
+
+ ); +}; + +export default chakra(SolidityscanReport); diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png new file mode 100644 index 0000000000..2cfbdaf3a7 Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc1155-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc1155-dark-mode-1.png deleted file mode 100644 index 8e21ba8758..0000000000 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc1155-dark-mode-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png index 5c9933e717..988d310f09 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc721-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc721-dark-mode-1.png deleted file mode 100644 index 4eca88970b..0000000000 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc721-dark-mode-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png new file mode 100644 index 0000000000..ff3f91748f Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png new file mode 100644 index 0000000000..185fa05a5c Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc1155-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc1155-dark-mode-1.png deleted file mode 100644 index 7cbc7815c5..0000000000 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc1155-dark-mode-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png index 405d4f9ef4..5cb20256d2 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc721-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc721-dark-mode-1.png deleted file mode 100644 index ad6f89f6c1..0000000000 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc721-dark-mode-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png new file mode 100644 index 0000000000..d2aacbe810 Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc1155-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc1155-1.png deleted file mode 100644 index 6c1d0b61ac..0000000000 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc1155-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png index 4be344fc59..9325e2a5b4 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc721-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc721-1.png deleted file mode 100644 index 9af951e2e9..0000000000 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc721-1.png and /dev/null differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png new file mode 100644 index 0000000000..cb01e73aa2 Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png new file mode 100644 index 0000000000..f4ca1946ec Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png index 21adb1d539..fab130576d 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png index 1292dc8384..4ada42569e 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png new file mode 100644 index 0000000000..f193eca4d4 Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png new file mode 100644 index 0000000000..b0bb931d9e Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png new file mode 100644 index 0000000000..671a01cb38 Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png new file mode 100644 index 0000000000..e3a2f18abc Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png new file mode 100644 index 0000000000..f75f69bc5b Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png new file mode 100644 index 0000000000..3668c60f8f Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png new file mode 100644 index 0000000000..69e2567395 Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png new file mode 100644 index 0000000000..3017e1181c Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png new file mode 100644 index 0000000000..5e7f44de24 Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png new file mode 100644 index 0000000000..4c8850cc5f Binary files /dev/null and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/contract/ContractCode.pw.tsx b/ui/address/contract/ContractCode.pw.tsx index ef4234d907..52c7fc9682 100644 --- a/ui/address/contract/ContractCode.pw.tsx +++ b/ui/address/contract/ContractCode.pw.tsx @@ -78,6 +78,31 @@ test('verified with changed byte code socket', async({ mount, page, createSocket await expect(component).toHaveScreenshot(); }); +test('verified via lookup in eth_bytecode_db', async({ mount, page, createSocket }) => { + await page.route(CONTRACT_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(contractMock.nonVerified), + })); + await page.route('https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/**', (route) => route.abort()); + + await mount( + + + , + { hooksConfig }, + ); + + const socket = await createSocket(); + const channel = await socketServer.joinChannel(socket, 'addresses:' + addressHash.toLowerCase()); + + await page.waitForResponse(CONTRACT_API_URL); + socketServer.sendMessage(socket, channel, 'smart_contract_was_verified', {}); + + const request = await page.waitForRequest(CONTRACT_API_URL); + + expect(request).toBeTruthy(); +}); + test('verified with multiple sources', async({ mount, page }) => { await page.route(CONTRACT_API_URL, (route) => route.fulfill({ status: 200, diff --git a/ui/address/contract/ContractCode.tsx b/ui/address/contract/ContractCode.tsx index 465602befd..cfa2e5f003 100644 --- a/ui/address/contract/ContractCode.tsx +++ b/ui/address/contract/ContractCode.tsx @@ -38,7 +38,6 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { const [ isChangedBytecodeSocket, setIsChangedBytecodeSocket ] = React.useState(); const queryClient = useQueryClient(); - const refetchQueries = queryClient.refetchQueries; const addressInfo = queryClient.getQueryData(getResourceKey('address', { pathParams: { hash: addressHash } })); const { data, isPlaceholderData, isError } = useApiQuery('contract', { @@ -55,13 +54,13 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { }, [ ]); const handleContractWasVerifiedMessage: SocketMessage.SmartContractWasVerified['handler'] = React.useCallback(() => { - refetchQueries({ + queryClient.refetchQueries({ queryKey: getResourceKey('address', { pathParams: { hash: addressHash } }), }); - refetchQueries({ + queryClient.refetchQueries({ queryKey: getResourceKey('contract', { pathParams: { hash: addressHash } }), }); - }, [ addressHash, refetchQueries ]); + }, [ addressHash, queryClient ]); const enableQuery = React.useCallback(() => setIsQueryEnabled(true), []); diff --git a/ui/address/contract/ContractConnectWallet.tsx b/ui/address/contract/ContractConnectWallet.tsx index eab09c0eaf..1673030a76 100644 --- a/ui/address/contract/ContractConnectWallet.tsx +++ b/ui/address/contract/ContractConnectWallet.tsx @@ -1,5 +1,5 @@ import { Alert, Button, Flex } from '@chakra-ui/react'; -import { useWeb3Modal } from '@web3modal/react'; +import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react'; import React from 'react'; import { useAccount, useDisconnect } from 'wagmi'; @@ -8,7 +8,8 @@ import * as mixpanel from 'lib/mixpanel/index'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; const ContractConnectWallet = () => { - const { open, isOpen } = useWeb3Modal(); + const { open } = useWeb3Modal(); + const { open: isOpen } = useWeb3ModalState(); const { disconnect } = useDisconnect(); const isMobile = useIsMobile(); const [ isModalOpening, setIsModalOpening ] = React.useState(false); @@ -17,11 +18,11 @@ const ContractConnectWallet = () => { setIsModalOpening(true); await open(); setIsModalOpening(false); - mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Status: 'Started' }); + mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: 'Smart contracts', Status: 'Started' }); }, [ open ]); const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { - !isReconnected && mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Status: 'Connected' }); + !isReconnected && mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: 'Smart contracts', Status: 'Connected' }); }, []); const handleDisconnect = React.useCallback(() => { diff --git a/ui/address/contract/ContractExternalLibraries.tsx b/ui/address/contract/ContractExternalLibraries.tsx index 0da60de207..4c89b5af69 100644 --- a/ui/address/contract/ContractExternalLibraries.tsx +++ b/ui/address/contract/ContractExternalLibraries.tsx @@ -4,7 +4,6 @@ import { Button, Flex, Heading, - Icon, Modal, ModalCloseButton, ModalContent, @@ -20,11 +19,10 @@ import React from 'react'; import type { SmartContractExternalLibrary } from 'types/api/contract'; -import arrowIcon from 'icons/arrows/east-mini.svg'; -import iconWarning from 'icons/status/warning.svg'; import useIsMobile from 'lib/hooks/useIsMobile'; import { apos } from 'lib/html-entities'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { className?: string; @@ -66,8 +64,8 @@ const ContractExternalLibraries = ({ className, data }: Props) => { aria-label="View external libraries" > { data.length } { data.length > 1 ? 'Libraries' : 'Library' } - - + + ); diff --git a/ui/address/contract/ContractMethodCallable.tsx b/ui/address/contract/ContractMethodCallable.tsx index f08a908088..62be6cda60 100644 --- a/ui/address/contract/ContractMethodCallable.tsx +++ b/ui/address/contract/ContractMethodCallable.tsx @@ -1,4 +1,4 @@ -import { Box, Button, chakra, Flex, Icon, Text } from '@chakra-ui/react'; +import { Box, Button, chakra, Flex } from '@chakra-ui/react'; import _fromPairs from 'lodash/fromPairs'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; @@ -7,8 +7,8 @@ import { useForm } from 'react-hook-form'; import type { MethodFormFields, ContractMethodCallResult } from './types'; import type { SmartContractMethodInput, SmartContractMethod } from 'types/api/contract'; -import arrowIcon from 'icons/arrows/down-right.svg'; import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; import ContractMethodField from './ContractMethodField'; @@ -157,8 +157,18 @@ const ContractMethodCallable = ({ data, onSubmit, { 'outputs' in data && !isWrite && data.outputs.length > 0 && ( - - { data.outputs.map(({ type }) => type).join(', ') } + +

+ { data.outputs.map(({ type, name }, index) => { + return ( + <> + { name } + { name ? `(${ type })` : type } + { index < data.outputs.length - 1 && , } + + ); + }) } +

) } { result && } diff --git a/ui/address/contract/ContractMethodFieldZeroes.tsx b/ui/address/contract/ContractMethodFieldZeroes.tsx index bf078260f8..1c0bfc9852 100644 --- a/ui/address/contract/ContractMethodFieldZeroes.tsx +++ b/ui/address/contract/ContractMethodFieldZeroes.tsx @@ -8,15 +8,13 @@ import { Button, List, ListItem, - Icon, useDisclosure, Input, } from '@chakra-ui/react'; import React from 'react'; -import iconEastMini from 'icons/arrows/east-mini.svg'; -import iconCheck from 'icons/check.svg'; import { times } from 'lib/html-entities'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { onClick: (power: number) => void; @@ -80,7 +78,7 @@ const ContractMethodFieldZeroes = ({ onClick, isDisabled }: Props) => { onClick={ onToggle } isDisabled={ isDisabled } > - + @@ -99,7 +97,7 @@ const ContractMethodFieldZeroes = ({ onClick, isDisabled }: Props) => { cursor="pointer" > 10*{ id } - { selectedOption === id && } + { selectedOption === id && } )) } { data: T; @@ -57,7 +57,7 @@ const ContractMethodsAccordionItem = ({ data, ind onMouseEnter={ onOpen } onMouseLeave={ onClose } > - +
) } diff --git a/ui/address/contract/ContractReadResult.pw.tsx b/ui/address/contract/ContractReadResult.pw.tsx index 8cd3341a70..35b4991a0c 100644 --- a/ui/address/contract/ContractReadResult.pw.tsx +++ b/ui/address/contract/ContractReadResult.pw.tsx @@ -99,3 +99,34 @@ test('success', async({ mount }) => { await expect(component).toHaveScreenshot(); }); + +test('complex success', async({ mount }) => { + const result: ContractMethodReadResult = { + is_error: false, + result: { + names: [ + [ + 'data', + [ 'totalSupply', 'owner', 'symbol' ], + ], + 'supports721', + 'page', + ], + output: [ + { + type: 'tuple[uint256,address,string]', + value: [ 1000, '0xe150519ae293922cfe6217feba3add4726f5e851', 'AOC_INCUBATORS' ], + }, + { type: 'bool', value: 'true' }, + { type: 'uint256[]', value: [ 1, 2, 3, 4, 5 ] }, + ], + }, + }; + const component = await mount( + + + , + ); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/address/contract/ContractReadResult.tsx b/ui/address/contract/ContractReadResult.tsx index 9ed7f5f481..28f701eb9b 100644 --- a/ui/address/contract/ContractReadResult.tsx +++ b/ui/address/contract/ContractReadResult.tsx @@ -2,17 +2,55 @@ import { Alert, Box, chakra, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; import type { ContractMethodReadResult } from './types'; -import type { SmartContractReadMethod } from 'types/api/contract'; +import type { SmartContractQueryMethodReadSuccess, SmartContractReadMethod } from 'types/api/contract'; import hexToUtf8 from 'lib/hexToUtf8'; +const TUPLE_TYPE_REGEX = /\[(.+)\]/; + const ContractReadResultError = ({ children }: {children: React.ReactNode}) => { return ( { children } ); +}; + +interface ItemProps { + output: SmartContractQueryMethodReadSuccess['result']['output'][0]; + name: SmartContractQueryMethodReadSuccess['result']['names'][0]; +} + +const ContractReadResultItem = ({ output, name }: ItemProps) => { + if (Array.isArray(name)) { + const [ structName, argNames ] = name; + const argTypes = output.type.match(TUPLE_TYPE_REGEX)?.[1].split(','); + return ( + <> +

+ { structName } + ({ output.type }) : +

+ { argNames.map((argName, argIndex) => { + return ( +

+ { argName } + { argTypes?.[argIndex] ? ` (${ argTypes[argIndex] })` : '' } : { String(output.value[argIndex]) } +

+ ); + }) } + + ); + } + + return ( +

+ + { name && { name } } + ({ output.type }) : { String(output.value) } +

+ ); }; interface Props { @@ -53,14 +91,12 @@ const ContractReadResult = ({ item, result, onSettle }: Props) => { } return ( - +

- [ { 'name' in item ? item.name : '' } method response ] + [ { 'name' in item ? item.name : '' } method response ]

[

- { result.result.output.map(({ type, value }, index) => ( - { type }: { String(value) } - )) } + { result.result.output.map((output, index) => ) }

]

); diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png index 8d23be81b2..c0e7093ca0 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png index 5d0c4a6419..9603bc33a2 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png index 7c0218683f..86d8bedf14 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png index 342f27e307..a76afca37a 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png index e0899c17b0..77ac115843 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png index 5f08f0bf14..06530635c0 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_complex-success-1.png b/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_complex-success-1.png new file mode 100644 index 0000000000..695b1df2c6 Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_complex-success-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_success-1.png b/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_success-1.png index 9ed2619edd..0010723b55 100644 Binary files a/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_success-1.png and b/ui/address/contract/__screenshots__/ContractReadResult.pw.tsx_default_success-1.png differ diff --git a/ui/address/details/AddressFavoriteButton.tsx b/ui/address/details/AddressFavoriteButton.tsx index b3c3c7cdad..8ce50e6c56 100644 --- a/ui/address/details/AddressFavoriteButton.tsx +++ b/ui/address/details/AddressFavoriteButton.tsx @@ -1,15 +1,14 @@ -import { Icon, chakra, Tooltip, IconButton, useDisclosure } from '@chakra-ui/react'; +import { chakra, Tooltip, IconButton, useDisclosure } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; import config from 'configs/app'; -import starFilledIcon from 'icons/star_filled.svg'; -import starOutlineIcon from 'icons/star_outline.svg'; import { getResourceKey } from 'lib/api/useApiQuery'; import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal'; import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal'; @@ -73,7 +72,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { pr="6px" flexShrink={ 0 } onClick={ handleClick } - icon={ } + icon={ } onFocusCapture={ onFocusCapture } /> diff --git a/ui/address/details/AddressQrCode.tsx b/ui/address/details/AddressQrCode.tsx index 62552f3a84..f3f58e546c 100644 --- a/ui/address/details/AddressQrCode.tsx +++ b/ui/address/details/AddressQrCode.tsx @@ -1,7 +1,6 @@ import { chakra, Alert, - Icon, Modal, ModalBody, ModalContent, @@ -22,10 +21,10 @@ import React from 'react'; import type { Address as AddressType } from 'types/api/address'; -import qrCodeIcon from 'icons/qr_code.svg'; import getPageType from 'lib/mixpanel/getPageType'; import * as mixpanel from 'lib/mixpanel/index'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import IconSvg from 'ui/shared/IconSvg'; const SVG_OPTIONS = { margin: 0, @@ -78,7 +77,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { pl="6px" pr="6px" onClick={ onOpen } - icon={ } + icon={ } flexShrink={ 0 } /> diff --git a/ui/address/internals/AddressIntTxsListItem.tsx b/ui/address/internals/AddressIntTxsListItem.tsx index 98284c78e5..463a7a053c 100644 --- a/ui/address/internals/AddressIntTxsListItem.tsx +++ b/ui/address/internals/AddressIntTxsListItem.tsx @@ -5,13 +5,12 @@ import React from 'react'; import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; -import eastArrowIcon from 'icons/arrows/east.svg'; import dayjs from 'lib/date/dayjs'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import IconSvg from 'ui/shared/IconSvg'; import InOutTag from 'ui/shared/InOutTag'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TxStatus from 'ui/shared/statusTag/TxStatus'; @@ -76,7 +75,7 @@ const TxInternalsListItem = ({ /> { (isIn || isOut) ? : - + } { toData && ( { (isIn || isOut) ? : - + } diff --git a/ui/address/tokenSelect/TokenSelect.pw.tsx b/ui/address/tokenSelect/TokenSelect.pw.tsx index 28e607dcdd..d2a810c981 100644 --- a/ui/address/tokenSelect/TokenSelect.pw.tsx +++ b/ui/address/tokenSelect/TokenSelect.pw.tsx @@ -2,10 +2,8 @@ import { Flex } from '@chakra-ui/react'; import { test as base, expect, devices } from '@playwright/experimental-ct-react'; import React from 'react'; -import * as coinBalanceMock from 'mocks/address/coinBalanceHistory'; import * as tokensMock from 'mocks/address/tokens'; import { tokenInfoERC20a } from 'mocks/tokens/tokenInfo'; -import * as socketServer from 'playwright/fixtures/socketServer'; import TestApp from 'playwright/TestApp'; import buildApiUrl from 'playwright/utils/buildApiUrl'; import MockAddressPage from 'ui/address/testUtils/MockAddressPage'; @@ -175,76 +173,3 @@ base('long values', async({ mount, page }) => { await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA }); }); - -test.describe('socket', () => { - const testWithSocket = test.extend({ - createSocket: socketServer.createSocket, - }); - testWithSocket.describe.configure({ mode: 'serial' }); - - testWithSocket('new item after token balance update', async({ page, mount, createSocket }) => { - await mount( - - - - - - - , - { hooksConfig }, - ); - - await page.route(TOKENS_ERC20_API_URL, async(route) => route.fulfill({ - status: 200, - body: JSON.stringify({ - items: [ - ...tokensMock.erc20List.items, - tokensMock.erc20d, - ], - }), - }), { times: 1 }); - - const socket = await createSocket(); - const channel = await socketServer.joinChannel(socket, 'addresses:1'); - socketServer.sendMessage(socket, channel, 'token_balance', { - block_number: 1, - }); - - const button = page.getByRole('button', { name: /select/i }); - const text = await button.innerText(); - expect(text).toContain('10'); - }); - - testWithSocket('new item after coin balance update', async({ page, mount, createSocket }) => { - await mount( - - - - - - - , - { hooksConfig }, - ); - - await page.route(TOKENS_ERC20_API_URL, async(route) => route.fulfill({ - status: 200, - body: JSON.stringify({ - items: [ - ...tokensMock.erc20List.items, - tokensMock.erc20d, - ], - }), - }), { times: 1 }); - - const socket = await createSocket(); - const channel = await socketServer.joinChannel(socket, 'addresses:1'); - socketServer.sendMessage(socket, channel, 'coin_balance', { - coin_balance: coinBalanceMock.base, - }); - - const button = page.getByRole('button', { name: /select/i }); - const text = await button.innerText(); - expect(text).toContain('10'); - }); -}); diff --git a/ui/address/tokenSelect/TokenSelect.tsx b/ui/address/tokenSelect/TokenSelect.tsx index 11b358e3f7..3cada7e253 100644 --- a/ui/address/tokenSelect/TokenSelect.tsx +++ b/ui/address/tokenSelect/TokenSelect.tsx @@ -1,20 +1,17 @@ -import { Box, Flex, Icon, IconButton, Skeleton, Tooltip } from '@chakra-ui/react'; +import { Box, Flex, IconButton, Skeleton, Tooltip } from '@chakra-ui/react'; import { useQueryClient, useIsFetching } from '@tanstack/react-query'; import _sumBy from 'lodash/sumBy'; import NextLink from 'next/link'; import { useRouter } from 'next/router'; import React from 'react'; -import type { SocketMessage } from 'lib/socket/types'; import type { Address } from 'types/api/address'; -import walletIcon from 'icons/wallet.svg'; import { getResourceKey } from 'lib/api/useApiQuery'; import useIsMobile from 'lib/hooks/useIsMobile'; import * as mixpanel from 'lib/mixpanel/index'; import getQueryParamString from 'lib/router/getQueryParamString'; -import useSocketChannel from 'lib/socket/useSocketChannel'; -import useSocketMessage from 'lib/socket/useSocketMessage'; +import IconSvg from 'ui/shared/IconSvg'; import useFetchTokens from '../utils/useFetchTokens'; import TokenSelectDesktop from './TokenSelectDesktop'; @@ -28,14 +25,13 @@ const TokenSelect = ({ onClick }: Props) => { const router = useRouter(); const isMobile = useIsMobile(); const queryClient = useQueryClient(); - const [ blockNumber, setBlockNumber ] = React.useState(); const addressHash = getQueryParamString(router.query.hash); const addressResourceKey = getResourceKey('address', { pathParams: { hash: addressHash } }); const addressQueryData = queryClient.getQueryData
(addressResourceKey); - const { data, isError, isPending, refetch } = useFetchTokens({ hash: addressQueryData?.hash }); + const { data, isError, isPending } = useFetchTokens({ hash: addressQueryData?.hash }); const tokensResourceKey = getResourceKey('address_tokens', { pathParams: { hash: addressQueryData?.hash }, queryParams: { type: 'ERC-20' } }); const tokensIsFetching = useIsFetching({ queryKey: tokensResourceKey }); @@ -44,34 +40,6 @@ const TokenSelect = ({ onClick }: Props) => { onClick?.(); }, [ onClick ]); - const handleTokenBalanceMessage: SocketMessage.AddressTokenBalance['handler'] = React.useCallback((payload) => { - if (payload.block_number !== blockNumber) { - refetch(); - setBlockNumber(payload.block_number); - } - }, [ blockNumber, refetch ]); - const handleCoinBalanceMessage: SocketMessage.AddressCoinBalance['handler'] = React.useCallback((payload) => { - if (payload.coin_balance.block_number !== blockNumber) { - refetch(); - setBlockNumber(payload.coin_balance.block_number); - } - }, [ blockNumber, refetch ]); - - const channel = useSocketChannel({ - topic: `addresses:${ addressQueryData?.hash.toLowerCase() }`, - isDisabled: !addressQueryData, - }); - useSocketMessage({ - channel, - event: 'coin_balance', - handler: handleCoinBalanceMessage, - }); - useSocketMessage({ - channel, - event: 'token_balance', - handler: handleTokenBalanceMessage, - }); - if (isPending) { return ( @@ -101,7 +69,7 @@ const TokenSelect = ({ onClick }: Props) => { size="sm" pl="6px" pr="6px" - icon={ } + icon={ } as="a" onClick={ handleIconButtonClick } /> diff --git a/ui/address/tokenSelect/TokenSelectButton.tsx b/ui/address/tokenSelect/TokenSelectButton.tsx index 03976be637..d58fdacea3 100644 --- a/ui/address/tokenSelect/TokenSelectButton.tsx +++ b/ui/address/tokenSelect/TokenSelectButton.tsx @@ -1,11 +1,10 @@ -import { Box, Button, Icon, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; +import { Box, Button, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; import type { FormattedData } from './types'; -import arrowIcon from 'icons/arrows/east-mini.svg'; -import tokensIcon from 'icons/tokens.svg'; import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; import { getTokensTotalInfo } from '../utils/tokenUtils'; @@ -41,10 +40,10 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea onClick={ handleClick } aria-label="Token select" > - + { prefix }{ num } ({ prefix }${ usd.toFormat(2) }) - + { isLoading && !isOpen && } diff --git a/ui/address/tokenSelect/TokenSelectItem.tsx b/ui/address/tokenSelect/TokenSelectItem.tsx index a1fffeac3f..bd9b9ea007 100644 --- a/ui/address/tokenSelect/TokenSelectItem.tsx +++ b/ui/address/tokenSelect/TokenSelectItem.tsx @@ -5,6 +5,7 @@ import React from 'react'; import { route } from 'nextjs-routes'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import LinkInternal from 'ui/shared/LinkInternal'; import TruncatedValue from 'ui/shared/TruncatedValue'; import type { TokenEnhancedData } from '../utils/tokenUtils'; @@ -47,11 +48,10 @@ const TokenSelectItem = ({ data }: Props) => { } })(); - // TODO add filter param when token page is ready const url = route({ pathname: '/token/[hash]', query: { hash: data.token.address } }); return ( - { _hover={{ bgColor: useColorModeValue('blue.50', 'gray.800'), }} + color="initial" fontSize="sm" - cursor="pointer" - as="a" href={ url } > @@ -80,7 +79,7 @@ const TokenSelectItem = ({ data }: Props) => { { secondRow } - + ); }; diff --git a/ui/address/tokenSelect/TokenSelectMenu.tsx b/ui/address/tokenSelect/TokenSelectMenu.tsx index 44699e711b..d247a0e3cd 100644 --- a/ui/address/tokenSelect/TokenSelectMenu.tsx +++ b/ui/address/tokenSelect/TokenSelectMenu.tsx @@ -1,4 +1,4 @@ -import { Icon, Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react'; +import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react'; import _sumBy from 'lodash/sumBy'; import type { ChangeEvent } from 'react'; import React from 'react'; @@ -6,8 +6,7 @@ import React from 'react'; import type { FormattedData } from './types'; import type { TokenType } from 'types/api/token'; -import arrowIcon from 'icons/arrows/east.svg'; -import searchIcon from 'icons/search.svg'; +import IconSvg from 'ui/shared/IconSvg'; import type { Sort } from '../utils/tokenUtils'; import { sortTokenGroups, sortingFns } from '../utils/tokenUtils'; @@ -32,7 +31,7 @@ const TokenSelectMenu = ({ erc20sort, erc1155sort, filteredData, onInputChange, <> - + { type } tokens ({ numPrefix }{ tokenInfo.items.length }) { hasSort && ( - + ) } diff --git a/ui/address/tokens/AddressCollections.tsx b/ui/address/tokens/AddressCollections.tsx new file mode 100644 index 0000000000..96b8326116 --- /dev/null +++ b/ui/address/tokens/AddressCollections.tsx @@ -0,0 +1,116 @@ +import { Box, Flex, Text, Grid, HStack, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import { route } from 'nextjs-routes'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import { apos } from 'lib/html-entities'; +import ActionBar from 'ui/shared/ActionBar'; +import DataListDisplay from 'ui/shared/DataListDisplay'; +import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import LinkInternal from 'ui/shared/LinkInternal'; +import NftFallback from 'ui/shared/nft/NftFallback'; +import Pagination from 'ui/shared/pagination/Pagination'; +import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; + +import NFTItem from './NFTItem'; +import NFTItemContainer from './NFTItemContainer'; + +type Props = { + collectionsQuery: QueryWithPagesResult<'address_collections'>; + address: string; + hasActiveFilters: boolean; +} + +const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Props) => { + const isMobile = useIsMobile(); + + const { isError, isPlaceholderData, data, pagination } = collectionsQuery; + + const actionBar = isMobile && pagination.isVisible && ( + + + + ); + + const content = data?.items ? data?.items.map((item, index) => { + const collectionUrl = route({ + pathname: '/token/[hash]', + query: { + hash: item.token.address, + tab: 'inventory', + holder_address_hash: address, + scroll_to_tabs: 'true', + }, + }); + const hasOverload = Number(item.amount) > item.token_instances.length; + return ( + + + + + { ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` } + + + View in collection + + + + { item.token_instances.map((instance, index) => { + const key = item.token.address + '_' + (instance.id && !isPlaceholderData ? `id_${ instance.id }` : `index_${ index }`); + + return ( + + ); + }) } + { hasOverload && ( + + + + + + + + View all NFTs + + + ) } + + + ); + }) : null; + + return ( + + ); +}; + +export default AddressCollections; diff --git a/ui/address/tokens/ERC1155Tokens.tsx b/ui/address/tokens/AddressNFTs.tsx similarity index 72% rename from ui/address/tokens/ERC1155Tokens.tsx rename to ui/address/tokens/AddressNFTs.tsx index 245fbec635..1c559e8d08 100644 --- a/ui/address/tokens/ERC1155Tokens.tsx +++ b/ui/address/tokens/AddressNFTs.tsx @@ -2,6 +2,7 @@ import { Grid } from '@chakra-ui/react'; import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; +import { apos } from 'lib/html-entities'; import ActionBar from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import Pagination from 'ui/shared/pagination/Pagination'; @@ -10,10 +11,11 @@ import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPage import NFTItem from './NFTItem'; type Props = { - tokensQuery: QueryWithPagesResult<'address_tokens'>; + tokensQuery: QueryWithPagesResult<'address_nfts'>; + hasActiveFilters: boolean; } -const ERC1155Tokens = ({ tokensQuery }: Props) => { +const AddressNFTs = ({ tokensQuery, hasActiveFilters }: Props) => { const isMobile = useIsMobile(); const { isError, isPlaceholderData, data, pagination } = tokensQuery; @@ -32,13 +34,14 @@ const ERC1155Tokens = ({ tokensQuery }: Props) => { gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }} > { data.items.map((item, index) => { - const key = item.token.address + '_' + (item.token_instance?.id && !isPlaceholderData ? `id_${ item.token_instance?.id }` : `index_${ index }`); + const key = item.token.address + '_' + (item.id && !isPlaceholderData ? `id_${ item.id }` : `index_${ index }`); return ( ); }) } @@ -52,8 +55,12 @@ const ERC1155Tokens = ({ tokensQuery }: Props) => { emptyText="There are no tokens of selected type." content={ content } actionBar={ actionBar } + filterProps={{ + emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`, + hasActiveFilters, + }} /> ); }; -export default ERC1155Tokens; +export default AddressNFTs; diff --git a/ui/address/tokens/ERC20TokensTableItem.tsx b/ui/address/tokens/ERC20TokensTableItem.tsx index be7ade69e4..ed78abcfd5 100644 --- a/ui/address/tokens/ERC20TokensTableItem.tsx +++ b/ui/address/tokens/ERC20TokensTableItem.tsx @@ -22,7 +22,13 @@ const ERC20TokensTableItem = ({ } = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 }); return ( - + - + diff --git a/ui/address/tokens/ERC721Tokens.tsx b/ui/address/tokens/ERC721Tokens.tsx deleted file mode 100644 index 8877703844..0000000000 --- a/ui/address/tokens/ERC721Tokens.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Show, Hide } from '@chakra-ui/react'; -import React from 'react'; - -import useIsMobile from 'lib/hooks/useIsMobile'; -import ActionBar from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import Pagination from 'ui/shared/pagination/Pagination'; -import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; - -import ERC721TokensListItem from './ERC721TokensListItem'; -import ERC721TokensTable from './ERC721TokensTable'; - -type Props = { - tokensQuery: QueryWithPagesResult<'address_tokens'>; -} - -const ERC721Tokens = ({ tokensQuery }: Props) => { - const isMobile = useIsMobile(); - - const { isError, isPlaceholderData, data, pagination } = tokensQuery; - - const actionBar = isMobile && pagination.isVisible && ( - - - - ); - - const content = data?.items ? ( - <> - - { data.items.map((item, index) => ( - - )) } - ) : null; - - return ( - - ); - -}; - -export default ERC721Tokens; diff --git a/ui/address/tokens/ERC721TokensListItem.tsx b/ui/address/tokens/ERC721TokensListItem.tsx deleted file mode 100644 index 66cf9e3911..0000000000 --- a/ui/address/tokens/ERC721TokensListItem.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Flex, HStack, Skeleton } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { AddressTokenBalance } from 'types/api/address'; - -import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; -import AddressEntity from 'ui/shared/entities/address/AddressEntity'; -import TokenEntityWithAddressFilter from 'ui/shared/entities/token/TokenEntityWithAddressFilter'; -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; - -type Props = AddressTokenBalance & { isLoading: boolean}; - -const ERC721TokensListItem = ({ token, value, isLoading }: Props) => { - const router = useRouter(); - - const hash = router.query.hash?.toString() || ''; - - return ( - - - - - - - - Quantity - - { value } - - - - ); -}; - -export default ERC721TokensListItem; diff --git a/ui/address/tokens/ERC721TokensTable.tsx b/ui/address/tokens/ERC721TokensTable.tsx deleted file mode 100644 index 9490e2cb58..0000000000 --- a/ui/address/tokens/ERC721TokensTable.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; -import React from 'react'; - -import type { AddressTokenBalance } from 'types/api/address'; - -import { default as Thead } from 'ui/shared/TheadSticky'; - -import ERC721TokensTableItem from './ERC721TokensTableItem'; - -interface Props { - data: Array; - top: number; - isLoading: boolean; -} - -const ERC721TokensTable = ({ data, top, isLoading }: Props) => { - return ( - - - - - - - - - - { data.map((item, index) => ( - - )) } - -
AssetContract addressQuantity
- ); -}; - -export default ERC721TokensTable; diff --git a/ui/address/tokens/ERC721TokensTableItem.tsx b/ui/address/tokens/ERC721TokensTableItem.tsx deleted file mode 100644 index fdb8c04611..0000000000 --- a/ui/address/tokens/ERC721TokensTableItem.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Tr, Td, Flex, Skeleton } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { AddressTokenBalance } from 'types/api/address'; - -import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; -import AddressEntity from 'ui/shared/entities/address/AddressEntity'; -import TokenEntityWithAddressFilter from 'ui/shared/entities/token/TokenEntityWithAddressFilter'; - -type Props = AddressTokenBalance & { isLoading: boolean}; - -const ERC721TokensTableItem = ({ - token, - value, - isLoading, -}: Props) => { - const router = useRouter(); - - const hash = router.query.hash?.toString() || ''; - - return ( - - - - - - - - - - - - - { value } - - - - ); -}; - -export default React.memo(ERC721TokensTableItem); diff --git a/ui/address/tokens/NFTItem.tsx b/ui/address/tokens/NFTItem.tsx index b4439d7f66..50a33ab73b 100644 --- a/ui/address/tokens/NFTItem.tsx +++ b/ui/address/tokens/NFTItem.tsx @@ -1,7 +1,7 @@ -import { Box, Flex, Text, Link, useColorModeValue } from '@chakra-ui/react'; +import { Tag, Flex, Text, Link, Skeleton, LightMode } from '@chakra-ui/react'; import React from 'react'; -import type { AddressTokenBalance } from 'types/api/address'; +import type { AddressNFT } from 'types/api/address'; import { route } from 'nextjs-routes'; @@ -9,22 +9,20 @@ import NftEntity from 'ui/shared/entities/nft/NftEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import NftMedia from 'ui/shared/nft/NftMedia'; -type Props = AddressTokenBalance & { isLoading: boolean }; +import NFTItemContainer from './NFTItemContainer'; -const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLoading }: Props) => { - const tokenInstanceLink = tokenId ? route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } }) : undefined; +type Props = AddressNFT & { isLoading: boolean; withTokenLink?: boolean }; + +const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: Props) => { + const tokenInstanceLink = tokenInstance.id ? + route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenInstance.id } }) : + undefined; return ( - + + + { token.type } + - { tokenId && ( - + + ID# - + + + { Number(value) > 1 && Qty { value } } + + + { withTokenLink && ( + ) } - - + ); }; diff --git a/ui/address/tokens/NFTItemContainer.tsx b/ui/address/tokens/NFTItemContainer.tsx new file mode 100644 index 0000000000..fc3d906d3e --- /dev/null +++ b/ui/address/tokens/NFTItemContainer.tsx @@ -0,0 +1,27 @@ +import { Box, useColorModeValue, chakra } from '@chakra-ui/react'; +import React from 'react'; + +type Props = { + children: React.ReactNode; + className?: string; +}; + +const NFTItemContainer = ({ children, className }: Props) => { + return ( + + { children } + + ); +}; + +export default chakra(NFTItemContainer); diff --git a/ui/address/tokens/TokenBalancesItem.tsx b/ui/address/tokens/TokenBalancesItem.tsx index 5d5918851d..7bf61d1860 100644 --- a/ui/address/tokens/TokenBalancesItem.tsx +++ b/ui/address/tokens/TokenBalancesItem.tsx @@ -1,7 +1,7 @@ -import { Box, Flex, Icon, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; -import walletIcon from 'icons/wallet.svg'; +import IconSvg from 'ui/shared/IconSvg'; const TokenBalancesItem = ({ name, value, isLoading }: {name: string; value: string; isLoading: boolean }) => { @@ -9,7 +9,7 @@ const TokenBalancesItem = ({ name, value, isLoading }: {name: string; value: str return ( - + { name } { value } diff --git a/ui/address/utils/useFetchTokens.ts b/ui/address/utils/useFetchTokens.ts index 9bc003373a..9b5bcda583 100644 --- a/ui/address/utils/useFetchTokens.ts +++ b/ui/address/utils/useFetchTokens.ts @@ -1,13 +1,25 @@ +import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; -import useApiQuery from 'lib/api/useApiQuery'; +import type { SocketMessage } from 'lib/socket/types'; +import type { AddressTokenBalance, AddressTokensBalancesSocketMessage, AddressTokensResponse } from 'types/api/address'; +import type { TokenType } from 'types/api/token'; -import { calculateUsdValue } from './tokenUtils'; +import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import useSocketChannel from 'lib/socket/useSocketChannel'; +import useSocketMessage from 'lib/socket/useSocketMessage'; +import { calculateUsdValue } from './tokenUtils'; interface Props { hash?: string; } +const tokenBalanceItemIdentityFactory = (match: AddressTokenBalance) => (item: AddressTokenBalance) => (( + match.token.address === item.token.address && + match.token_id === item.token_id && + match.token_instance?.id === item.token_instance?.id +)); + export default function useFetchTokens({ hash }: Props) { const erc20query = useApiQuery('address_tokens', { pathParams: { hash }, @@ -25,11 +37,67 @@ export default function useFetchTokens({ hash }: Props) { queryOptions: { enabled: Boolean(hash), refetchOnMount: false }, }); - const refetch = React.useCallback(() => { - erc20query.refetch(); - erc721query.refetch(); - erc1155query.refetch(); - }, [ erc1155query, erc20query, erc721query ]); + const queryClient = useQueryClient(); + + const updateTokensData = React.useCallback((type: TokenType, payload: AddressTokensBalancesSocketMessage) => { + const queryKey = getResourceKey('address_tokens', { pathParams: { hash }, queryParams: { type } }); + + queryClient.setQueryData(queryKey, (prevData: AddressTokensResponse | undefined) => { + const items = prevData?.items.map((currentItem) => { + const updatedData = payload.token_balances.find(tokenBalanceItemIdentityFactory(currentItem)); + return updatedData ?? currentItem; + }) || []; + + const extraItems = prevData?.next_page_params ? + [] : + payload.token_balances.filter((socketItem) => !items.some(tokenBalanceItemIdentityFactory(socketItem))); + + if (!prevData) { + return { + items: extraItems, + next_page_params: null, + }; + } + + return { + items: items.concat(extraItems), + next_page_params: prevData.next_page_params, + }; + }); + }, [ hash, queryClient ]); + + const handleTokenBalancesErc20Message: SocketMessage.AddressTokenBalancesErc20['handler'] = React.useCallback((payload) => { + updateTokensData('ERC-20', payload); + }, [ updateTokensData ]); + + const handleTokenBalancesErc721Message: SocketMessage.AddressTokenBalancesErc721['handler'] = React.useCallback((payload) => { + updateTokensData('ERC-721', payload); + }, [ updateTokensData ]); + + const handleTokenBalancesErc1155Message: SocketMessage.AddressTokenBalancesErc1155['handler'] = React.useCallback((payload) => { + updateTokensData('ERC-1155', payload); + }, [ updateTokensData ]); + + const channel = useSocketChannel({ + topic: `addresses:${ hash?.toLowerCase() }`, + isDisabled: Boolean(hash) && (erc20query.isPlaceholderData || erc721query.isPlaceholderData || erc1155query.isPlaceholderData), + }); + + useSocketMessage({ + channel, + event: 'updated_token_balances_erc_20', + handler: handleTokenBalancesErc20Message, + }); + useSocketMessage({ + channel, + event: 'updated_token_balances_erc_721', + handler: handleTokenBalancesErc721Message, + }); + useSocketMessage({ + channel, + event: 'updated_token_balances_erc_1155', + handler: handleTokenBalancesErc1155Message, + }); const data = React.useMemo(() => { return { @@ -52,6 +120,5 @@ export default function useFetchTokens({ hash }: Props) { isPending: erc20query.isPending || erc721query.isPending || erc1155query.isPending, isError: erc20query.isError || erc721query.isError || erc1155query.isError, data, - refetch, }; } diff --git a/ui/addressVerification/AddressVerificationModal.tsx b/ui/addressVerification/AddressVerificationModal.tsx index 601ec982e9..b70d472dc7 100644 --- a/ui/addressVerification/AddressVerificationModal.tsx +++ b/ui/addressVerification/AddressVerificationModal.tsx @@ -1,11 +1,11 @@ -import { Icon, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Link } from '@chakra-ui/react'; +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Link } from '@chakra-ui/react'; import React from 'react'; import type { AddressVerificationFormFirstStepFields, AddressCheckStatusSuccess } from './types'; import type { VerifiedAddress } from 'types/api/account'; -import eastArrowIcon from 'icons/arrows/east.svg'; import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import AddressVerificationStepAddress from './steps/AddressVerificationStepAddress'; @@ -100,7 +100,7 @@ const AddressVerificationModal = ({ defaultAddress, isOpen, onClose, onSubmit, o { stepIndex !== 0 && ( - + ) } { step.title } diff --git a/ui/addressVerification/steps/AddressVerificationStepSignature.tsx b/ui/addressVerification/steps/AddressVerificationStepSignature.tsx index af77a4bda7..0c779f4880 100644 --- a/ui/addressVerification/steps/AddressVerificationStepSignature.tsx +++ b/ui/addressVerification/steps/AddressVerificationStepSignature.tsx @@ -1,5 +1,5 @@ import { Alert, Box, Button, chakra, Flex, Link, Radio, RadioGroup } from '@chakra-ui/react'; -import { useWeb3Modal } from '@web3modal/react'; +import { useWeb3Modal } from '@web3modal/wagmi/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm } from 'react-hook-form'; diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index fb8b969466..af4fe1b3ff 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -11,8 +11,6 @@ import type { Block } from 'types/api/block'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import clockIcon from 'icons/clock.svg'; -import flameIcon from 'icons/flame.svg'; import type { ResourceError } from 'lib/api/resources'; import getBlockReward from 'lib/block/getBlockReward'; import { GWEI, WEI, WEI_IN_GWEI, ZERO } from 'lib/consts'; @@ -20,7 +18,6 @@ import dayjs from 'lib/date/dayjs'; import { space } from 'lib/html-entities'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getQueryParamString from 'lib/router/getQueryParamString'; -import Icon from 'ui/shared/chakra/Icon'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; @@ -28,6 +25,7 @@ import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; import PrevNext from 'ui/shared/PrevNext'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; @@ -169,7 +167,7 @@ const BlockDetails = ({ query }: Props) => { hint="Date & time at which block was produced." isLoading={ isPlaceholderData } > - + { dayjs(data.timestamp).fromNow() } @@ -202,19 +200,21 @@ const BlockDetails = ({ query }: Props) => { ) } - - - { /* api doesn't return the block processing time yet */ } - { /* { dayjs.duration(block.minedIn, 'second').humanize(true) } */ } - + > + + { /* api doesn't return the block processing time yet */ } + { /* { dayjs.duration(block.minedIn, 'second').humanize(true) } */ } + + ) } { !isRollup && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && ( { } isLoading={ isPlaceholderData } > - + { burntFees.dividedBy(WEI).toFixed() } { config.chain.currency.symbol } diff --git a/ui/blocks/BlocksListItem.tsx b/ui/blocks/BlocksListItem.tsx index 2097823c8a..8b5bcb6c2e 100644 --- a/ui/blocks/BlocksListItem.tsx +++ b/ui/blocks/BlocksListItem.tsx @@ -8,15 +8,14 @@ import type { Block } from 'types/api/block'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import flameIcon from 'icons/flame.svg'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import { WEI } from 'lib/consts'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import BlockTimestamp from 'ui/blocks/BlockTimestamp'; -import Icon from 'ui/shared/chakra/Icon'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TextSeparator from 'ui/shared/TextSeparator'; @@ -57,13 +56,15 @@ const BlocksListItem = ({ data, isLoading, enableTimeIncrement }: Props) => { { data.size.toLocaleString() } bytes - - { capitalize(getNetworkValidatorTitle()) } - - + { !config.UI.views.block.hiddenFields?.miner && ( + + { capitalize(getNetworkValidatorTitle()) } + + + ) } Txn { data.tx_count > 0 ? ( @@ -104,7 +105,7 @@ const BlocksListItem = ({ data, isLoading, enableTimeIncrement }: Props) => { Burnt fees - + { burntFees.div(WEI).toFixed() } diff --git a/ui/blocks/BlocksTable.tsx b/ui/blocks/BlocksTable.tsx index 7540aff714..594fc98777 100644 --- a/ui/blocks/BlocksTable.tsx +++ b/ui/blocks/BlocksTable.tsx @@ -42,7 +42,8 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum Block Size, bytes - { capitalize(getNetworkValidatorTitle()) } + { !config.UI.views.block.hiddenFields?.miner && + { capitalize(getNetworkValidatorTitle()) } } Txn Gas used { !isRollup && !config.UI.views.block.hiddenFields?.total_reward && diff --git a/ui/blocks/BlocksTableItem.tsx b/ui/blocks/BlocksTableItem.tsx index 17c0710bbc..2081d416dd 100644 --- a/ui/blocks/BlocksTableItem.tsx +++ b/ui/blocks/BlocksTableItem.tsx @@ -8,14 +8,13 @@ import type { Block } from 'types/api/block'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import flameIcon from 'icons/flame.svg'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import { WEI } from 'lib/consts'; import BlockTimestamp from 'ui/blocks/BlockTimestamp'; -import Icon from 'ui/shared/chakra/Icon'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; import TextSeparator from 'ui/shared/TextSeparator'; import Utilization from 'ui/shared/Utilization/Utilization'; @@ -66,12 +65,14 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { { data.size.toLocaleString() } - - - + { !config.UI.views.block.hiddenFields?.miner && ( + + + + ) } { data.tx_count > 0 ? ( @@ -114,7 +115,7 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => { { !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && ( - + { burntFees.dividedBy(WEI).toFixed(8) } diff --git a/ui/contractVerification/ContractVerificationForm.tsx b/ui/contractVerification/ContractVerificationForm.tsx index e6392edfc1..2a464a017e 100644 --- a/ui/contractVerification/ContractVerificationForm.tsx +++ b/ui/contractVerification/ContractVerificationForm.tsx @@ -1,21 +1,23 @@ -import { Button, chakra, useUpdateEffect } from '@chakra-ui/react'; +import { Button, Grid, chakra, useUpdateEffect } from '@chakra-ui/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; import type { FormFields } from './types'; import type { SocketMessage } from 'lib/socket/types'; -import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract'; +import type { SmartContractVerificationMethod, SmartContractVerificationConfig, SmartContract } from 'types/api/contract'; import { route } from 'nextjs-routes'; import useApiFetch from 'lib/api/useApiFetch'; import delay from 'lib/delay'; +import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode'; import useToast from 'lib/hooks/useToast'; import * as mixpanel from 'lib/mixpanel/index'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; +import ContractVerificationFieldAddress from './fields/ContractVerificationFieldAddress'; import ContractVerificationFieldMethod from './fields/ContractVerificationFieldMethod'; import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode'; import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile'; @@ -29,15 +31,15 @@ import { prepareRequestBody, formatSocketErrors, getDefaultValues, METHOD_LABELS interface Props { method?: SmartContractVerificationMethod; config: SmartContractVerificationConfig; - hash: string; + hash?: string; } const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Props) => { const formApi = useForm({ mode: 'onBlur', - defaultValues: methodFromQuery ? getDefaultValues(methodFromQuery, config) : undefined, + defaultValues: methodFromQuery ? getDefaultValues(methodFromQuery, config, hash) : undefined, }); - const { control, handleSubmit, watch, formState, setError, reset } = formApi; + const { control, handleSubmit, watch, formState, setError, reset, getFieldState } = formApi; const submitPromiseResolver = React.useRef<(value: unknown) => void>(); const methodNameRef = React.useRef(); @@ -47,9 +49,28 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro const onFormSubmit: SubmitHandler = React.useCallback(async(data) => { const body = prepareRequestBody(data); + if (!hash) { + try { + const response = await apiFetch<'contract', SmartContract>('contract', { + pathParams: { hash: data.address.toLowerCase() }, + }); + + const isVerifiedContract = 'is_verified' in response && response?.is_verified && !response.is_partially_verified; + if (isVerifiedContract) { + setError('address', { message: 'Contract has already been verified' }); + return Promise.resolve(); + } + } catch (error) { + const statusCode = getErrorObjStatusCode(error); + const message = statusCode === 404 ? 'Address is not a smart contract' : 'Something went wrong'; + setError('address', { message }); + return Promise.resolve(); + } + } + try { await apiFetch('contract_verification_via', { - pathParams: { method: data.method.value, hash: hash.toLowerCase() }, + pathParams: { method: data.method.value, hash: data.address.toLowerCase() }, fetchParams: { method: 'POST', body, @@ -62,7 +83,10 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro return new Promise((resolve) => { submitPromiseResolver.current = resolve; }); - }, [ apiFetch, hash ]); + }, [ apiFetch, hash, setError ]); + + const address = watch('address'); + const addressState = getFieldState('address'); const handleNewSocketMessage: SocketMessage.ContractVerification['handler'] = React.useCallback(async(payload) => { if (payload.status === 'error') { @@ -88,8 +112,8 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro { send_immediately: true }, ); - window.location.assign(route({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } })); - }, [ hash, setError, toast ]); + window.location.assign(route({ pathname: '/address/[hash]', query: { hash: address, tab: 'contract' } })); + }, [ setError, toast, address ]); const handleSocketError = React.useCallback(() => { if (!formState.isSubmitting) { @@ -114,10 +138,10 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro }, [ toast ]); const channel = useSocketChannel({ - topic: `addresses:${ hash.toLowerCase() }`, + topic: `addresses:${ address?.toLowerCase() }`, onSocketClose: handleSocketError, onSocketError: handleSocketError, - isDisabled: false, + isDisabled: Boolean(address && addressState.error), }); useSocketMessage({ channel, @@ -142,7 +166,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro useUpdateEffect(() => { if (methodValue) { - reset(getDefaultValues(methodValue, config)); + reset(getDefaultValues(methodValue, config, address || hash)); const methodName = METHOD_LABELS[methodValue]; mixpanel.logEvent(mixpanel.EventTypes.CONTRACT_VERIFICATION, { Status: 'Method selected', Method: methodName }); @@ -157,11 +181,14 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro noValidate onSubmit={ handleSubmit(onFormSubmit) } > - + + { !hash && } + + { content } { Boolean(method) && ( + + + + + + ); +}; + +export default MarketplaceDisclaimerModal; diff --git a/ui/marketplace/MarketplaceList.tsx b/ui/marketplace/MarketplaceList.tsx index 6660fe42b2..98abb23dd6 100644 --- a/ui/marketplace/MarketplaceList.tsx +++ b/ui/marketplace/MarketplaceList.tsx @@ -14,9 +14,10 @@ type Props = { favoriteApps: Array; onFavoriteClick: (id: string, isFavorite: boolean) => void; isLoading: boolean; + showDisclaimer: (id: string) => void; } -const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLoading }: Props) => { +const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLoading, showDisclaimer }: Props) => { return apps.length > 0 ? ( )) } diff --git a/ui/marketplace/useMarketplace.tsx b/ui/marketplace/useMarketplace.tsx index 204baefa1b..7c8c86b9ea 100644 --- a/ui/marketplace/useMarketplace.tsx +++ b/ui/marketplace/useMarketplace.tsx @@ -29,6 +29,8 @@ export default function useMarketplace() { const [ selectedCategoryId, setSelectedCategoryId ] = React.useState(MarketplaceCategory.ALL); const [ filterQuery, setFilterQuery ] = React.useState(defaultFilterQuery); const [ favoriteApps, setFavoriteApps ] = React.useState>([]); + const [ isAppInfoModalOpen, setIsAppInfoModalOpen ] = React.useState(false); + const [ isDisclaimerModalOpen, setIsDisclaimerModalOpen ] = React.useState(false); const handleFavoriteClick = React.useCallback((id: string, isFavorite: boolean) => { const favoriteApps = getFavoriteApps(); @@ -46,10 +48,20 @@ export default function useMarketplace() { const showAppInfo = React.useCallback((id: string) => { setSelectedAppId(id); + setIsAppInfoModalOpen(true); + }, []); + + const showDisclaimer = React.useCallback((id: string) => { + setSelectedAppId(id); + setIsDisclaimerModalOpen(true); }, []); const debouncedFilterQuery = useDebounce(filterQuery, 500); - const clearSelectedAppId = React.useCallback(() => setSelectedAppId(null), []); + const clearSelectedAppId = React.useCallback(() => { + setSelectedAppId(null); + setIsAppInfoModalOpen(false); + setIsDisclaimerModalOpen(false); + }, []); const handleCategoryChange = React.useCallback((newCategory: string) => { setSelectedCategoryId(newCategory); @@ -104,6 +116,9 @@ export default function useMarketplace() { clearSelectedAppId, favoriteApps, onFavoriteClick: handleFavoriteClick, + isAppInfoModalOpen, + isDisclaimerModalOpen, + showDisclaimer, }), [ selectedCategoryId, categories, @@ -118,5 +133,8 @@ export default function useMarketplace() { isPlaceholderData, showAppInfo, debouncedFilterQuery, + isAppInfoModalOpen, + isDisclaimerModalOpen, + showDisclaimer, ]); } diff --git a/ui/marketplace/useMarketplaceWallet.tsx b/ui/marketplace/useMarketplaceWallet.tsx new file mode 100644 index 0000000000..17370ea6bd --- /dev/null +++ b/ui/marketplace/useMarketplaceWallet.tsx @@ -0,0 +1,64 @@ +import type { TypedData } from 'abitype'; +import { useCallback } from 'react'; +import type { Account, SignTypedDataParameters } from 'viem'; +import { useAccount, useSendTransaction, useSwitchNetwork, useNetwork, useSignMessage, useSignTypedData } from 'wagmi'; + +import config from 'configs/app'; + +type SendTransactionArgs = { + chainId?: number; + mode?: 'prepared'; + to: string; +}; + +export type SignTypedDataArgs< + TTypedData extends + | TypedData + | { + [key: string]: unknown; + } = TypedData, + TPrimaryType extends string = string, +> = SignTypedDataParameters; + +export default function useMarketplaceWallet() { + const { address } = useAccount(); + const { chain } = useNetwork(); + const { sendTransactionAsync } = useSendTransaction(); + const { signMessageAsync } = useSignMessage(); + const { signTypedDataAsync } = useSignTypedData(); + const { switchNetworkAsync } = useSwitchNetwork({ chainId: Number(config.chain.id) }); + + const switchNetwork = useCallback(async() => { + if (Number(config.chain.id) !== chain?.id) { + await switchNetworkAsync?.(); + } + }, [ chain, switchNetworkAsync ]); + + const sendTransaction = useCallback(async(transaction: SendTransactionArgs) => { + await switchNetwork(); + const tx = await sendTransactionAsync(transaction); + return tx.hash; + }, [ sendTransactionAsync, switchNetwork ]); + + const signMessage = useCallback(async(message: string) => { + await switchNetwork(); + const signature = await signMessageAsync({ message }); + return signature; + }, [ signMessageAsync, switchNetwork ]); + + const signTypedData = useCallback(async(typedData: SignTypedDataArgs) => { + await switchNetwork(); + if (typedData.domain) { + typedData.domain.chainId = Number(typedData.domain.chainId); + } + const signature = await signTypedDataAsync(typedData); + return signature; + }, [ signTypedDataAsync, switchNetwork ]); + + return { + address, + sendTransaction, + signMessage, + signTypedData, + }; +} diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index be89c410b0..f5e75429f1 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -1,12 +1,10 @@ -import { Box, Flex, Icon } from '@chakra-ui/react'; +import { Box, Flex, HStack } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; -import type { TokenType } from 'types/api/token'; import type { RoutedTab } from 'ui/shared/Tabs/types'; import config from 'configs/app'; -import iconSuccess from 'icons/status/success.svg'; import useApiQuery from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import useContractTabs from 'lib/hooks/useContractTabs'; @@ -25,23 +23,19 @@ import AddressTxs from 'ui/address/AddressTxs'; import AddressWithdrawals from 'ui/address/AddressWithdrawals'; import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton'; import AddressQrCode from 'ui/address/details/AddressQrCode'; +import SolidityscanReport from 'ui/address/SolidityscanReport'; import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; import TextAd from 'ui/shared/ad/TextAd'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import EntityTags from 'ui/shared/EntityTags'; +import IconSvg from 'ui/shared/IconSvg'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; -export const tokenTabsByType: Record = { - 'ERC-20': 'tokens_erc20', - 'ERC-721': 'tokens_erc721', - 'ERC-1155': 'tokens_erc1155', -} as const; - -const TOKEN_TABS = Object.values(tokenTabsByType); +const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ]; const AddressPageContent = () => { const router = useRouter(); @@ -133,7 +127,7 @@ const AddressPageContent = () => { return ( <> Contract - + ); } @@ -194,7 +188,9 @@ const AddressPageContent = () => { ) } - + + { addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled && } + ); diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index c880c372c4..a1bbb094c5 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -24,7 +24,7 @@ import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { marginBottom: 0, @@ -82,7 +82,7 @@ const BlockPageContent = () => { const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', component: }, - { id: 'txs', title: 'Transactions', component: }, + { id: 'txs', title: 'Transactions', component: }, config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ? { id: 'withdrawals', title: 'Withdrawals', component: } : null, @@ -116,20 +116,22 @@ const BlockPageContent = () => { const title = blockQuery.data?.type === 'reorg' ? `Reorged block #${ blockQuery.data?.height }` : `Block #${ blockQuery.data?.height }`; const titleSecondRow = ( <> - - - { config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by' } - - - - + { !config.UI.views.block.hiddenFields?.miner && ( + + + { config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by' } + + + + ) } + ); diff --git a/ui/pages/ContractVerification.tsx b/ui/pages/ContractVerification.tsx index a2bdfc3d47..0262da2397 100644 --- a/ui/pages/ContractVerification.tsx +++ b/ui/pages/ContractVerification.tsx @@ -1,109 +1,31 @@ -import { useRouter } from 'next/router'; import React from 'react'; -import type { SmartContractVerificationMethod } from 'types/api/contract'; - -import useApiQuery from 'lib/api/useApiQuery'; -import { useAppContext } from 'lib/contexts/app'; -import getQueryParamString from 'lib/router/getQueryParamString'; import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm'; -import { isValidVerificationMethod, sortVerificationMethods } from 'ui/contractVerification/utils'; +import useFormConfigQuery from 'ui/contractVerification/useFormConfigQuery'; import ContentLoader from 'ui/shared/ContentLoader'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import PageTitle from 'ui/shared/Page/PageTitle'; const ContractVerification = () => { - const appProps = useAppContext(); - const router = useRouter(); - - const hash = getQueryParamString(router.query.hash); - const method = getQueryParamString(router.query.method) as SmartContractVerificationMethod; - - const contractQuery = useApiQuery('contract', { - pathParams: { hash }, - queryOptions: { - enabled: Boolean(hash), - }, - }); - - if (contractQuery.isError && contractQuery.error.status === 404) { - throw Error('Not found', { cause: contractQuery.error as unknown as Error }); - } - - const configQuery = useApiQuery('contract_verification_config', { - queryOptions: { - select: (data) => { - return { - ...data, - verification_options: data.verification_options.filter(isValidVerificationMethod).sort(sortVerificationMethods), - }; - }, - enabled: Boolean(hash), - }, - }); - - React.useEffect(() => { - if (method && hash) { - router.replace({ pathname: '/address/[hash]/contract-verification', query: { hash } }, undefined, { scroll: false, shallow: true }); - } - // onMount only - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ ]); - - const isVerifiedContract = contractQuery.data?.is_verified && !contractQuery.data.is_partially_verified; - - React.useEffect(() => { - if (isVerifiedContract) { - router.push({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } }, undefined, { scroll: false, shallow: true }); - } - }, [ hash, isVerifiedContract, router ]); + const configQuery = useFormConfigQuery(true); const content = (() => { - if (configQuery.isError || !hash || contractQuery.isError) { + if (configQuery.isError) { return ; } - if (configQuery.isPending || contractQuery.isPending || isVerifiedContract) { + if (configQuery.isPending) { return ; } return ( - + ); })(); - const backLink = React.useMemo(() => { - const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/address'); - - if (!hasGoBackLink) { - return; - } - - return { - label: 'Back to contract', - url: appProps.referrer, - }; - }, [ appProps.referrer ]); - return ( <> - - + { content } ); diff --git a/ui/pages/ContractVerificationForAddress.tsx b/ui/pages/ContractVerificationForAddress.tsx new file mode 100644 index 0000000000..f11c58efb0 --- /dev/null +++ b/ui/pages/ContractVerificationForAddress.tsx @@ -0,0 +1,102 @@ +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { SmartContractVerificationMethod } from 'types/api/contract'; + +import useApiQuery from 'lib/api/useApiQuery'; +import { useAppContext } from 'lib/contexts/app'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm'; +import useFormConfigQuery from 'ui/contractVerification/useFormConfigQuery'; +import ContentLoader from 'ui/shared/ContentLoader'; +import DataFetchAlert from 'ui/shared/DataFetchAlert'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import PageTitle from 'ui/shared/Page/PageTitle'; + +const ContractVerificationForAddress = () => { + const appProps = useAppContext(); + const router = useRouter(); + + const hash = getQueryParamString(router.query.hash); + const method = getQueryParamString(router.query.method) as SmartContractVerificationMethod; + + const contractQuery = useApiQuery('contract', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash), + }, + }); + + if (contractQuery.isError && contractQuery.error.status === 404) { + throw Error('Not found', { cause: contractQuery.error as unknown as Error }); + } + + const configQuery = useFormConfigQuery(Boolean(hash)); + + React.useEffect(() => { + if (method && hash) { + router.replace({ pathname: '/address/[hash]/contract-verification', query: { hash } }, undefined, { scroll: false, shallow: true }); + } + // onMount only + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ ]); + + const isVerifiedContract = contractQuery.data?.is_verified && !contractQuery.data.is_partially_verified; + + React.useEffect(() => { + if (isVerifiedContract) { + router.push({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } }, undefined, { scroll: false, shallow: true }); + } + }, [ hash, isVerifiedContract, router ]); + + const content = (() => { + if (configQuery.isError || !hash || contractQuery.isError) { + return ; + } + + if (configQuery.isPending || contractQuery.isPending || isVerifiedContract) { + return ; + } + + return ( + + ); + })(); + + const backLink = React.useMemo(() => { + const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/address'); + + if (!hasGoBackLink) { + return; + } + + return { + label: 'Back to contract', + url: appProps.referrer, + }; + }, [ appProps.referrer ]); + + return ( + <> + + + { content } + + ); +}; + +export default ContractVerificationForAddress; diff --git a/ui/pages/Home.tsx b/ui/pages/Home.tsx index dfd6c958af..14d9cc2684 100644 --- a/ui/pages/Home.tsx +++ b/ui/pages/Home.tsx @@ -1,4 +1,4 @@ -import { Box, Heading, Flex, LightMode } from '@chakra-ui/react'; +import { Box, Heading, Flex } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; @@ -10,6 +10,7 @@ import Transactions from 'ui/home/Transactions'; import AdBanner from 'ui/shared/ad/AdBanner'; import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; import SearchBar from 'ui/snippets/searchBar/SearchBar'; +import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; const Home = () => { return ( @@ -22,7 +23,7 @@ const Home = () => { minW={{ base: 'unset', lg: '900px' }} data-label="hero plate" > - + { fontWeight={ 600 } color={ config.UI.homepage.plate.textColor } > - Welcome to { config.chain.name } explorer + { config.chain.name } explorer - - { config.features.account.isEnabled && } + + { config.features.account.isEnabled && } + { config.features.blockchainInteraction.isEnabled && } - - - + diff --git a/ui/pages/KettleTxs.tsx b/ui/pages/KettleTxs.tsx index 0e2444dc4e..8866bced13 100644 --- a/ui/pages/KettleTxs.tsx +++ b/ui/pages/KettleTxs.tsx @@ -7,7 +7,7 @@ import { generateListStub } from 'stubs/utils'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const KettleTxs = () => { const router = useRouter(); @@ -31,7 +31,7 @@ const KettleTxs = () => { <> - diff --git a/ui/pages/Marketplace.tsx b/ui/pages/Marketplace.tsx index 499f72fe66..7024498b39 100644 --- a/ui/pages/Marketplace.tsx +++ b/ui/pages/Marketplace.tsx @@ -1,10 +1,10 @@ -import { Box, Icon, Link, Skeleton } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; -import PlusIcon from 'icons/plus.svg'; import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal'; import MarketplaceCategoriesMenu from 'ui/marketplace/MarketplaceCategoriesMenu'; +import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal'; import MarketplaceList from 'ui/marketplace/MarketplaceList'; import FilterInput from 'ui/shared/filters/FilterInput'; @@ -27,6 +27,9 @@ const Marketplace = () => { clearSelectedAppId, favoriteApps, onFavoriteClick, + isAppInfoModalOpen, + isDisclaimerModalOpen, + showDisclaimer, } = useMarketplace(); if (isError) { @@ -68,9 +71,10 @@ const Marketplace = () => { favoriteApps={ favoriteApps } onFavoriteClick={ onFavoriteClick } isLoading={ isPlaceholderData } + showDisclaimer={ showDisclaimer } /> - { selectedApp && ( + { (selectedApp && isAppInfoModalOpen) && ( { /> ) } - - - - - Submit an app - - + { (selectedApp && isDisclaimerModalOpen) && ( + + ) } ); }; diff --git a/ui/pages/MarketplaceApp.tsx b/ui/pages/MarketplaceApp.tsx index c206c5c9f7..44d87aa0b2 100644 --- a/ui/pages/MarketplaceApp.tsx +++ b/ui/pages/MarketplaceApp.tsx @@ -1,7 +1,8 @@ import { Box, Center, useColorMode } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; +import { DappscoutIframeProvider, useDappscoutIframe } from 'dappscout-iframe'; import { useRouter } from 'next/router'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import type { MarketplaceAppOverview } from 'types/client/marketplace'; @@ -9,12 +10,12 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; -import { useAppContext } from 'lib/contexts/app'; import useApiFetch from 'lib/hooks/useFetch'; import * as metadata from 'lib/metadata'; import getQueryParamString from 'lib/router/getQueryParamString'; import ContentLoader from 'ui/shared/ContentLoader'; -import PageTitle from 'ui/shared/Page/PageTitle'; + +import useMarketplaceWallet from '../marketplace/useMarketplaceWallet'; const feature = config.features.marketplace; const configUrl = feature.isEnabled ? feature.configUrl : ''; @@ -26,35 +27,23 @@ const IFRAME_SANDBOX_ATTRIBUTE = 'allow-forms allow-orientation-lock ' + const IFRAME_ALLOW_ATTRIBUTE = 'clipboard-read; clipboard-write;'; -const MarketplaceApp = () => { - const ref = useRef(null); - - const apiFetch = useApiFetch(); - const appProps = useAppContext(); - const router = useRouter(); - const id = getQueryParamString(router.query.id); - - const { isPending, isError, error, data } = useQuery, MarketplaceAppOverview>({ - queryKey: [ 'marketplace-apps', id ], - queryFn: async() => { - const result = await apiFetch, unknown>(configUrl, undefined, { resource: 'marketplace-apps' }); - if (!Array.isArray(result)) { - throw result; - } - - const item = result.find((app: MarketplaceAppOverview) => app.id === id); - if (!item) { - throw { status: 404 }; - } +type Props = { + address: string | undefined; + data: MarketplaceAppOverview | undefined; + isPending: boolean; +}; - return item; - }, - enabled: feature.isEnabled, - }); +const MarketplaceAppContent = ({ address, data, isPending }: Props) => { + const { iframeRef, isReady } = useDappscoutIframe(); + const [ iframeKey, setIframeKey ] = useState(0); const [ isFrameLoading, setIsFrameLoading ] = useState(isPending); const { colorMode } = useColorMode(); + useEffect(() => { + setIframeKey((key) => key + 1); + }, [ address ]); + const handleIframeLoad = useCallback(() => { setIsFrameLoading(false); }, []); @@ -72,9 +61,61 @@ const MarketplaceApp = () => { blockscoutNetworkRpc: config.chain.rpcUrl, }; - ref?.current?.contentWindow?.postMessage(message, data.url); + iframeRef?.current?.contentWindow?.postMessage(message, data.url); } - }, [ isFrameLoading, data, colorMode, ref ]); + }, [ isFrameLoading, data, colorMode, iframeRef ]); + + return ( +
+ { (isFrameLoading) && ( + + ) } + + { (data && isReady) && ( + + ) } +
+ ); +}; + +const MarketplaceApp = () => { + const { address, sendTransaction, signMessage, signTypedData } = useMarketplaceWallet(); + + const apiFetch = useApiFetch(); + const router = useRouter(); + const id = getQueryParamString(router.query.id); + + const { isPending, isError, error, data } = useQuery, MarketplaceAppOverview>({ + queryKey: [ 'marketplace-apps', id ], + queryFn: async() => { + const result = await apiFetch, unknown>(configUrl, undefined, { resource: 'marketplace-apps' }); + if (!Array.isArray(result)) { + throw result; + } + const item = result.find((app: MarketplaceAppOverview) => app.id === id); + if (!item) { + throw { status: 404 }; + } + + return item; + }, + enabled: feature.isEnabled, + }); useEffect(() => { if (data) { @@ -89,46 +130,17 @@ const MarketplaceApp = () => { throw new Error('Unable to load app', { cause: error }); } - const backLink = React.useMemo(() => { - const hasGoBackLink = appProps.referrer.includes('/apps'); - - if (!hasGoBackLink) { - return; - } - - return { - label: 'Back to marketplace', - url: appProps.referrer, - }; - }, [ appProps.referrer ]); - return ( - <> - { !isPending && } -
- { (isFrameLoading) && ( - - ) } - - { data && ( - - ) } -
- + + + ); }; diff --git a/ui/pages/SearchResults.pw.tsx b/ui/pages/SearchResults.pw.tsx index 1add342996..4e78e6f35f 100644 --- a/ui/pages/SearchResults.pw.tsx +++ b/ui/pages/SearchResults.pw.tsx @@ -8,8 +8,6 @@ import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import TestApp from 'playwright/TestApp'; import * as app from 'playwright/utils/app'; import buildApiUrl from 'playwright/utils/buildApiUrl'; -import * as configs from 'playwright/utils/configs'; -import LayoutMainColumn from 'ui/shared/layout/components/MainColumn'; import SearchResults from './SearchResults'; @@ -47,17 +45,12 @@ test.describe('search by name ', () => { const component = await mount( - - - + , { hooksConfig }, ); - await expect(component).toHaveScreenshot({ - mask: [ page.locator('header'), page.locator('form') ], - maskColor: configs.maskColor, - }); + await expect(component.locator('main')).toHaveScreenshot(); }); }); @@ -78,17 +71,12 @@ test('search by address hash +@mobile', async({ mount, page }) => { const component = await mount( - - - + , { hooksConfig }, ); - await expect(component).toHaveScreenshot({ - mask: [ page.locator('header'), page.locator('form') ], - maskColor: configs.maskColor, - }); + await expect(component.locator('main')).toHaveScreenshot(); }); test('search by block number +@mobile', async({ mount, page }) => { @@ -109,17 +97,12 @@ test('search by block number +@mobile', async({ mount, page }) => { const component = await mount( - - - + , { hooksConfig }, ); - await expect(component).toHaveScreenshot({ - mask: [ page.locator('header'), page.locator('form') ], - maskColor: configs.maskColor, - }); + await expect(component.locator('main')).toHaveScreenshot(); }); test('search by block hash +@mobile', async({ mount, page }) => { @@ -139,17 +122,12 @@ test('search by block hash +@mobile', async({ mount, page }) => { const component = await mount( - - - + , { hooksConfig }, ); - await expect(component).toHaveScreenshot({ - mask: [ page.locator('header'), page.locator('form') ], - maskColor: configs.maskColor, - }); + await expect(component.locator('main')).toHaveScreenshot(); }); test('search by tx hash +@mobile', async({ mount, page }) => { @@ -169,17 +147,12 @@ test('search by tx hash +@mobile', async({ mount, page }) => { const component = await mount( - - - + , { hooksConfig }, ); - await expect(component).toHaveScreenshot({ - mask: [ page.locator('header'), page.locator('form') ], - maskColor: configs.maskColor, - }); + await expect(component.locator('main')).toHaveScreenshot(); }); test.describe('with apps', () => { @@ -228,16 +201,11 @@ test.describe('with apps', () => { const component = await mount( - - - + , { hooksConfig }, ); - await expect(component).toHaveScreenshot({ - mask: [ page.locator('header'), page.locator('form') ], - maskColor: configs.maskColor, - }); + await expect(component.locator('main')).toHaveScreenshot(); }); }); diff --git a/ui/pages/SearchResults.tsx b/ui/pages/SearchResults.tsx index 7948065e34..51e86fde9a 100644 --- a/ui/pages/SearchResults.tsx +++ b/ui/pages/SearchResults.tsx @@ -15,8 +15,9 @@ import * as Layout from 'ui/shared/layout/components'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; import Thead from 'ui/shared/TheadSticky'; -import Header from 'ui/snippets/header/Header'; import HeaderAlert from 'ui/snippets/header/HeaderAlert'; +import HeaderDesktop from 'ui/snippets/header/HeaderDesktop'; +import HeaderMobile from 'ui/snippets/header/HeaderMobile'; import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery'; const SearchResultsPageContent = () => { @@ -181,13 +182,20 @@ const SearchResultsPageContent = () => { return ( <> - -
- - - { pageContent } - - + + + + + + + + + { pageContent } + + + + + ); }; diff --git a/ui/pages/Token.tsx b/ui/pages/Token.tsx index a2e3dd8c58..aedc8ce28e 100644 --- a/ui/pages/Token.tsx +++ b/ui/pages/Token.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Icon, Tooltip } from '@chakra-ui/react'; +import { Box, Flex, Tooltip } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React, { useEffect } from 'react'; @@ -9,8 +9,6 @@ import type { PaginationParams } from 'ui/shared/pagination/types'; import type { RoutedTab } from 'ui/shared/Tabs/types'; import config from 'configs/app'; -import iconSuccess from 'icons/status/success.svg'; -import iconVerifiedToken from 'icons/verified_token.svg'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import useContractTabs from 'lib/hooks/useContractTabs'; @@ -30,6 +28,7 @@ import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EntityTags from 'ui/shared/EntityTags'; +import IconSvg from 'ui/shared/IconSvg'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; @@ -56,6 +55,7 @@ const TokenPageContent = () => { const hashString = getQueryParamString(router.query.hash); const tab = getQueryParamString(router.query.tab); + const ownerFilter = getQueryParamString(router.query.holder_address_hash) || undefined; const queryClient = useQueryClient(); @@ -140,6 +140,7 @@ const TokenPageContent = () => { const inventoryQuery = useQueryWithPages({ resourceName: 'token_inventory', pathParams: { hash: hashString }, + filters: ownerFilter ? { holder_address_hash: ownerFilter } : {}, scrollRef, options: { enabled: Boolean( @@ -150,7 +151,7 @@ const TokenPageContent = () => { tab === 'inventory' ), ), - placeholderData: generateListStub<'token_inventory'>(tokenStubs.TOKEN_INSTANCE, 50, { next_page_params: null }), + placeholderData: generateListStub<'token_inventory'>(tokenStubs.TOKEN_INSTANCE, 50, { next_page_params: { unique_token: 1 } }), }, }); @@ -173,9 +174,11 @@ const TokenPageContent = () => { const contractTabs = useContractTabs(contractQuery.data); const tabs: Array = [ - (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ? - { id: 'inventory', title: 'Inventory', component: } : - undefined, + (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ? { + id: 'inventory', + title: 'Inventory', + component: , + } : undefined, { id: 'token_transfers', title: 'Token transfers', component: }, { id: 'holders', title: 'Holders', component: }, contractQuery.data?.is_contract ? { @@ -185,7 +188,7 @@ const TokenPageContent = () => { return ( <> Contract - + ); } @@ -246,7 +249,7 @@ const TokenPageContent = () => { { verifiedInfoQuery.data?.tokenAddress && ( - + ) } diff --git a/ui/pages/TokenInstance.tsx b/ui/pages/TokenInstance.tsx index 63fadd075b..38e938e504 100644 --- a/ui/pages/TokenInstance.tsx +++ b/ui/pages/TokenInstance.tsx @@ -10,7 +10,7 @@ import { useAppContext } from 'lib/contexts/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import * as metadata from 'lib/metadata'; import * as regexp from 'lib/regexp'; -import { TOKEN_INSTANCE } from 'stubs/token'; +import { TOKEN_INSTANCE, TOKEN_INFO_ERC_1155 } from 'stubs/token'; import * as tokenStubs from 'stubs/token'; import { generateListStub } from 'stubs/utils'; import AddressQrCode from 'ui/address/details/AddressQrCode'; @@ -43,6 +43,14 @@ const TokenInstanceContent = () => { const scrollRef = React.useRef(null); + const tokenQuery = useApiQuery('token', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash && id), + placeholderData: TOKEN_INFO_ERC_1155, + }, + }); + const tokenInstanceQuery = useApiQuery('token_instance', { pathParams: { hash, id }, queryOptions: { @@ -58,14 +66,18 @@ const TokenInstanceContent = () => { options: { enabled: Boolean(hash && id && (!tab || tab === 'token_transfers') && !tokenInstanceQuery.isPlaceholderData && tokenInstanceQuery.data), placeholderData: generateListStub<'token_instance_transfers'>( - tokenInstanceQuery.data?.token.type === 'ERC-1155' ? tokenStubs.TOKEN_TRANSFER_ERC_1155 : tokenStubs.TOKEN_TRANSFER_ERC_721, + tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_TRANSFER_ERC_1155 : tokenStubs.TOKEN_TRANSFER_ERC_721, 10, { next_page_params: null }, ), }, }); - const shouldFetchHolders = !tokenInstanceQuery.isPlaceholderData && tokenInstanceQuery.data && !tokenInstanceQuery.data.is_unique; + const shouldFetchHolders = + !tokenQuery.isPlaceholderData && + !tokenInstanceQuery.isPlaceholderData && + tokenInstanceQuery.data && + !tokenInstanceQuery.data.is_unique; const holdersQuery = useQueryWithPages({ resourceName: 'token_instance_holders', @@ -74,18 +86,18 @@ const TokenInstanceContent = () => { options: { enabled: Boolean(hash && tab === 'holders' && shouldFetchHolders), placeholderData: generateListStub<'token_instance_holders'>( - tokenInstanceQuery.data?.token.type === 'ERC-1155' ? tokenStubs.TOKEN_HOLDER_ERC_1155 : tokenStubs.TOKEN_HOLDER_ERC_20, 10, { next_page_params: null }), + tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_HOLDER_ERC_1155 : tokenStubs.TOKEN_HOLDER_ERC_20, 10, { next_page_params: null }), }, }); React.useEffect(() => { - if (tokenInstanceQuery.data && !tokenInstanceQuery.isPlaceholderData) { + if (tokenInstanceQuery.data && !tokenInstanceQuery.isPlaceholderData && tokenQuery.data && !tokenQuery.isPlaceholderData) { metadata.update( - { pathname: '/token/[hash]/instance/[id]', query: { hash: tokenInstanceQuery.data.token.address, id: tokenInstanceQuery.data.id } }, - { symbol: tokenInstanceQuery.data.token.symbol ?? '' }, + { pathname: '/token/[hash]/instance/[id]', query: { hash: tokenQuery.data.address, id: tokenInstanceQuery.data.id } }, + { symbol: tokenQuery.data.symbol ?? '' }, ); } - }, [ tokenInstanceQuery.data, tokenInstanceQuery.isPlaceholderData ]); + }, [ tokenInstanceQuery.data, tokenInstanceQuery.isPlaceholderData, tokenQuery.data, tokenQuery.isPlaceholderData ]); const backLink = React.useMemo(() => { const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`) && !appProps.referrer.includes('instance'); @@ -104,10 +116,10 @@ const TokenInstanceContent = () => { { id: 'token_transfers', title: 'Token transfers', - component: , + component: , }, shouldFetchHolders ? - { id: 'holders', title: 'Holders', component: } : + { id: 'holders', title: 'Holders', component: } : undefined, { id: 'metadata', title: 'Metadata', component: ( { throw Error('Token instance fetch failed', { cause: tokenInstanceQuery.error }); } - const tokenTag = { tokenInstanceQuery.data?.token.type }; + const tokenTag = { tokenQuery.data?.type }; const address = { hash: hash || '', @@ -131,7 +143,7 @@ const TokenInstanceContent = () => { watchlist_address_id: null, }; - const isLoading = tokenInstanceQuery.isPlaceholderData; + const isLoading = tokenInstanceQuery.isPlaceholderData || tokenQuery.isPlaceholderData; const appLink = (() => { if (!tokenInstanceQuery.data?.external_app_url) { @@ -168,7 +180,7 @@ const TokenInstanceContent = () => { const titleSecondRow = ( { w="auto" maxW="700px" /> - { !isLoading && tokenInstanceQuery.data && } + { !isLoading && tokenInstanceQuery.data && } { appLink } @@ -197,7 +209,7 @@ const TokenInstanceContent = () => { isLoading={ isLoading } /> - + { /* should stay before tabs to scroll up with pagination */ } diff --git a/ui/pages/Tokens.tsx b/ui/pages/Tokens.tsx index 2442470b37..2db3e0d8c7 100644 --- a/ui/pages/Tokens.tsx +++ b/ui/pages/Tokens.tsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import React from 'react'; import type { TokenType } from 'types/api/token'; -import type { TokensSortingValue } from 'types/api/tokens'; +import type { TokensSortingValue, TokensSortingField, TokensSorting } from 'types/api/tokens'; import type { RoutedTab } from 'ui/shared/Tabs/types'; import config from 'configs/app'; @@ -16,11 +16,13 @@ import PopoverFilter from 'ui/shared/filters/PopoverFilter'; import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; +import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TokensList from 'ui/tokens/Tokens'; import TokensActionBar from 'ui/tokens/TokensActionBar'; import TokensBridgedChainsFilter from 'ui/tokens/TokensBridgedChainsFilter'; -import { getSortParamsFromValue, getSortValueFromQuery, getTokenFilterValue, getBridgedChainsFilterValue } from 'ui/tokens/utils'; +import { SORT_OPTIONS, getTokenFilterValue, getBridgedChainsFilterValue } from 'ui/tokens/utils'; const TAB_LIST_PROPS = { marginBottom: 0, @@ -44,7 +46,7 @@ const Tokens = () => { const q = getQueryParamString(router.query.q); const [ searchTerm, setSearchTerm ] = React.useState(q ?? ''); - const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query)); + const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS)); const [ tokenTypes, setTokenTypes ] = React.useState | undefined>(getTokenFilterValue(router.query.type)); const [ bridgeChains, setBridgeChains ] = React.useState | undefined>(getBridgedChainsFilterValue(router.query.chain_ids)); @@ -53,7 +55,7 @@ const Tokens = () => { const tokensQuery = useQueryWithPages({ resourceName: tab === 'bridged' ? 'tokens_bridged' : 'tokens', filters: tab === 'bridged' ? { q: debouncedSearchTerm, chain_ids: bridgeChains } : { q: debouncedSearchTerm, type: tokenTypes }, - sorting: getSortParamsFromValue(sort), + sorting: getSortParamsFromValue(sort), options: { placeholderData: generateListStub<'tokens'>( TOKEN_INFO_ERC_20, @@ -105,7 +107,7 @@ const Tokens = () => { ) : ( 0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }> - + onChange={ handleTokenTypesChange } defaultValue={ tokenTypes } nftOnly={ false }/> ); diff --git a/ui/pages/Transaction.tsx b/ui/pages/Transaction.tsx index 894006b550..57328ecc58 100644 --- a/ui/pages/Transaction.tsx +++ b/ui/pages/Transaction.tsx @@ -8,11 +8,8 @@ import useApiQuery from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import getQueryParamString from 'lib/router/getQueryParamString'; import { TX } from 'stubs/tx'; -import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; import TextAd from 'ui/shared/ad/TextAd'; -import TxEntity from 'ui/shared/entities/tx/TxEntity'; import EntityTags from 'ui/shared/EntityTags'; -import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from 'ui/shared/Page/PageTitle'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; @@ -23,6 +20,7 @@ import TxInternals from 'ui/tx/TxInternals'; import TxLogs from 'ui/tx/TxLogs'; import TxRawTrace from 'ui/tx/TxRawTrace'; import TxState from 'ui/tx/TxState'; +import TxSubHeading from 'ui/tx/TxSubHeading'; import TxTokenTransfer from 'ui/tx/TxTokenTransfer'; const TransactionPageContent = () => { @@ -40,7 +38,11 @@ const TransactionPageContent = () => { }); const tabs: Array = [ - { id: 'index', title: config.features.suave.isEnabled && data?.wrapped ? 'Confidential compute tx details' : 'Details', component: }, + { + id: 'index', + title: config.features.suave.isEnabled && data?.wrapped ? 'Confidential compute tx details' : 'Details', + component: , + }, config.features.suave.isEnabled && data?.wrapped ? { id: 'wrapped', title: 'Regular tx details', component: } : undefined, @@ -73,13 +75,7 @@ const TransactionPageContent = () => { }; }, [ appProps.referrer ]); - const titleSecondRow = ( - <> - - { !data?.tx_tag && } - - - ); + const titleSecondRow = ; return ( <> diff --git a/ui/pages/Transactions.tsx b/ui/pages/Transactions.tsx index 19fc842934..2d7027aa47 100644 --- a/ui/pages/Transactions.tsx +++ b/ui/pages/Transactions.tsx @@ -13,8 +13,8 @@ import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; -import TxsContent from 'ui/txs/TxsContent'; import TxsWatchlist from 'ui/txs/TxsWatchlist'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { marginBottom: 0, @@ -60,12 +60,13 @@ const Transactions = () => { { id: 'validated', title: verifiedTitle, - component: }, + component: + }, { id: 'pending', title: 'Pending', component: ( - { return ( - + Verify your email address Please confirm your email address to use the My Account feature. A confirmation email was sent to diff --git a/ui/pages/VerifiedContracts.tsx b/ui/pages/VerifiedContracts.tsx index c7c6d46c98..9003c9a181 100644 --- a/ui/pages/VerifiedContracts.tsx +++ b/ui/pages/VerifiedContracts.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router'; import React from 'react'; import type { VerifiedContractsFilters } from 'types/api/contracts'; +import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts'; import useDebounce from 'lib/hooks/useDebounce'; import useIsMobile from 'lib/hooks/useIsMobile'; @@ -16,9 +17,10 @@ import FilterInput from 'ui/shared/filters/FilterInput'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; +import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; import Sort from 'ui/shared/sort/Sort'; -import type { SortField, Sort as TSort } from 'ui/verifiedContracts/utils'; -import { SORT_OPTIONS, sortFn, getNextSortValue } from 'ui/verifiedContracts/utils'; +import { SORT_OPTIONS } from 'ui/verifiedContracts/utils'; import VerifiedContractsCounters from 'ui/verifiedContracts/VerifiedContractsCounters'; import VerifiedContractsFilter from 'ui/verifiedContracts/VerifiedContractsFilter'; import VerifiedContractsList from 'ui/verifiedContracts/VerifiedContractsList'; @@ -28,15 +30,17 @@ const VerifiedContracts = () => { const router = useRouter(); const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.q) || undefined); const [ type, setType ] = React.useState(getQueryParamString(router.query.filter) as VerifiedContractsFilters['filter'] || undefined); - const [ sort, setSort ] = React.useState(); + const [ sort, setSort ] = + React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS)); const debouncedSearchTerm = useDebounce(searchTerm || '', 300); const isMobile = useIsMobile(); - const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({ + const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({ resourceName: 'verified_contracts', filters: { q: debouncedSearchTerm, filter: type }, + sorting: getSortParamsFromValue(sort), options: { placeholderData: generateListStub<'verified_contracts'>( VERIFIED_CONTRACT_INFO, @@ -67,11 +71,10 @@ const VerifiedContracts = () => { setType(filter); }, [ debouncedSearchTerm, onFilterChange ]); - const handleSortToggle = React.useCallback((field: SortField) => { - return () => { - setSort(getNextSortValue(field)); - }; - }, []); + const handleSortChange = React.useCallback((value?: VerifiedContractsSortingValue) => { + setSort(value); + onSortingChange(getSortParamsFromValue(value)); + }, [ onSortingChange ]); const typeFilter = ; @@ -89,7 +92,7 @@ const VerifiedContracts = () => { ); @@ -112,15 +115,13 @@ const VerifiedContracts = () => { ); - const sortedData = data?.items.slice().sort(sortFn(sort)); - - const content = sortedData ? ( + const content = data?.items ? ( <> - + - + ) : null; diff --git a/ui/pages/ZkEvmL2TxnBatch.tsx b/ui/pages/ZkEvmL2TxnBatch.tsx index e2cb9811ff..388703bf41 100644 --- a/ui/pages/ZkEvmL2TxnBatch.tsx +++ b/ui/pages/ZkEvmL2TxnBatch.tsx @@ -14,7 +14,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; import ZkEvmL2TxnBatchDetails from 'ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails'; const ZkEvmL2TxnBatch = () => { @@ -51,7 +51,7 @@ const ZkEvmL2TxnBatch = () => { const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', component: }, - { id: 'txs', title: 'Transactions', component: }, + { id: 'txs', title: 'Transactions', component: }, ].filter(Boolean)), [ batchQuery, batchTxsQuery ]); const backLink = React.useMemo(() => { diff --git a/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png b/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png index 38b843009f..e10ae1d684 100644 Binary files a/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_custom-hero-plate-background-default-view-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_custom-hero-plate-background-default-view-1.png index 717d7b4241..1851dc3744 100644 Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_custom-hero-plate-background-default-view-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_custom-hero-plate-background-default-view-1.png differ diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png index e762dae528..06cdd3d820 100644 Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png differ diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png index 20df208c57..f5fd2e4c57 100644 Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_dark-color-mode_search-by-name-mobile-dark-mode-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_dark-color-mode_search-by-name-mobile-dark-mode-1.png index db609b1b37..f218590790 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_dark-color-mode_search-by-name-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_dark-color-mode_search-by-name-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-address-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-address-hash-mobile-1.png index a436dcf95a..d88025d48d 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-address-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-address-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-hash-mobile-1.png index cbafec575b..650e6f77af 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png index f59d33b8ec..5b1c2cc61b 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-name-mobile-dark-mode-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-name-mobile-dark-mode-1.png index ad427ae423..0f70ac15c8 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-name-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-name-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-tx-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-tx-hash-mobile-1.png index 505c1000df..77929a19f8 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-tx-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-tx-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_with-apps-default-view-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_with-apps-default-view-mobile-1.png index 0f96ac8480..70e925a163 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_with-apps-default-view-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_with-apps-default-view-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-address-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-address-hash-mobile-1.png index d183555d25..4a4bcc4ec3 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-address-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-address-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-hash-mobile-1.png index 2cdbda73c8..9e8c7114c3 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png index c6db02b758..61706c1465 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-name-mobile-dark-mode-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-name-mobile-dark-mode-1.png index c30a26c228..4a67cc56a9 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-name-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-name-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-tx-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-tx-hash-mobile-1.png index de91502d7e..129fef00aa 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-tx-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-tx-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_with-apps-default-view-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_with-apps-default-view-mobile-1.png index c5e5031d20..eb326db091 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_with-apps-default-view-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_with-apps-default-view-mobile-1.png differ diff --git a/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_address-verification-flow-1.png b/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_address-verification-flow-1.png index 7adbd13a6c..fb50f6c924 100644 Binary files a/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_address-verification-flow-1.png and b/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_address-verification-flow-1.png differ diff --git a/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_base-view-mobile-1.png index bda2779dd5..0ea5398010 100644 Binary files a/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_mobile_base-view-mobile-1.png index 38ea0e7023..33b6c84d8e 100644 Binary files a/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/VerifiedAddresses.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/publicTags/PublicTagsForm/PublicTagFormAddressInput.tsx b/ui/publicTags/PublicTagsForm/PublicTagFormAddressInput.tsx index 6be2543dd9..c6dc9e52d9 100644 --- a/ui/publicTags/PublicTagsForm/PublicTagFormAddressInput.tsx +++ b/ui/publicTags/PublicTagsForm/PublicTagFormAddressInput.tsx @@ -1,13 +1,12 @@ import type { InputProps } from '@chakra-ui/react'; -import { IconButton, Icon, Flex } from '@chakra-ui/react'; +import { IconButton, Flex } from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { ControllerRenderProps, Control, FieldError } from 'react-hook-form'; import { Controller } from 'react-hook-form'; -import MinusIcon from 'icons/minus.svg'; -import PlusIcon from 'icons/plus.svg'; import { ADDRESS_REGEXP } from 'lib/validations/address'; import AddressInput from 'ui/shared/AddressInput'; +import IconSvg from 'ui/shared/IconSvg'; import type { Inputs } from './PublicTagsForm'; @@ -60,7 +59,7 @@ export default function PublicTagFormAction({ control, index, fieldsLength, erro w="30px" h="30px" onClick={ onRemoveFieldClick(index) } - icon={ } + icon={ } /> ) } { index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && ( @@ -70,7 +69,7 @@ export default function PublicTagFormAction({ control, index, fieldsLength, erro w="30px" h="30px" onClick={ onAddFieldClick } - icon={ } + icon={ } /> ) } diff --git a/ui/searchResults/SearchResultListItem.tsx b/ui/searchResults/SearchResultListItem.tsx index 363dec5ea5..b6d14f0a85 100644 --- a/ui/searchResults/SearchResultListItem.tsx +++ b/ui/searchResults/SearchResultListItem.tsx @@ -1,4 +1,4 @@ -import { Flex, Grid, Icon, Image, Box, Text, Skeleton, useColorMode, Tag } from '@chakra-ui/react'; +import { Flex, Grid, Image, Box, Text, Skeleton, useColorMode, Tag } from '@chakra-ui/react'; import React from 'react'; import xss from 'xss'; @@ -6,9 +6,6 @@ import type { SearchResultItem } from 'types/api/search'; import { route } from 'nextjs-routes'; -import labelIcon from 'icons/publictags_slim.svg'; -import iconSuccess from 'icons/status/success.svg'; -import verifiedToken from 'icons/verified_token.svg'; import dayjs from 'lib/date/dayjs'; import highlightText from 'lib/highlightText'; import * as mixpanel from 'lib/mixpanel/index'; @@ -18,6 +15,7 @@ import * as BlockEntity from 'ui/shared/entities/block/BlockEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TxEntity from 'ui/shared/entities/tx/TxEntity'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; import LinkInternal from 'ui/shared/LinkInternal'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -68,7 +66,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { textOverflow="ellipsis" /> - { data.is_verified_via_admin_panel && } + { data.is_verified_via_admin_panel && } ); } @@ -107,7 +105,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { case 'label': { return ( - + { - { data.is_smart_contract_verified && } + { data.is_smart_contract_verified && } - + { data.token_type === 'ERC-20' && data.exchange_rate && `$${ Number(data.exchange_rate).toLocaleString() }` } { data.token_type !== 'ERC-20' && data.total_supply && `Items ${ Number(data.total_supply).toLocaleString() }` } - + ); } @@ -245,7 +243,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { - { data.is_smart_contract_verified && } + { data.is_smart_contract_verified && } ); } diff --git a/ui/searchResults/SearchResultTableItem.tsx b/ui/searchResults/SearchResultTableItem.tsx index 505cbfb460..b91e337df8 100644 --- a/ui/searchResults/SearchResultTableItem.tsx +++ b/ui/searchResults/SearchResultTableItem.tsx @@ -1,4 +1,4 @@ -import { Tr, Td, Text, Flex, Icon, Image, Box, Skeleton, useColorMode, Tag } from '@chakra-ui/react'; +import { Tr, Td, Text, Flex, Image, Box, Skeleton, useColorMode, Tag } from '@chakra-ui/react'; import React from 'react'; import xss from 'xss'; @@ -6,9 +6,6 @@ import type { SearchResultItem } from 'types/api/search'; import { route } from 'nextjs-routes'; -import labelIcon from 'icons/publictags_slim.svg'; -import iconSuccess from 'icons/status/success.svg'; -import verifiedToken from 'icons/verified_token.svg'; import dayjs from 'lib/date/dayjs'; import highlightText from 'lib/highlightText'; import * as mixpanel from 'lib/mixpanel/index'; @@ -18,6 +15,7 @@ import * as BlockEntity from 'ui/shared/entities/block/BlockEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TxEntity from 'ui/shared/entities/tx/TxEntity'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; import LinkInternal from 'ui/shared/LinkInternal'; import type { SearchResultAppItem } from 'ui/shared/search/utils'; @@ -68,7 +66,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }} /> - { data.is_verified_via_admin_panel && } + { data.is_verified_via_admin_panel && } @@ -76,7 +74,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { - { data.is_smart_contract_verified && } + { data.is_smart_contract_verified && } @@ -165,7 +163,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { <> - + { - { data.is_smart_contract_verified && } + { data.is_smart_contract_verified && } diff --git a/ui/shared/AccountActionsMenu/AccountActionsMenu.tsx b/ui/shared/AccountActionsMenu/AccountActionsMenu.tsx index b8fdb8fac2..a74da37045 100644 --- a/ui/shared/AccountActionsMenu/AccountActionsMenu.tsx +++ b/ui/shared/AccountActionsMenu/AccountActionsMenu.tsx @@ -1,12 +1,12 @@ -import { Button, Menu, MenuButton, MenuList, Icon, Flex, Skeleton, chakra } from '@chakra-ui/react'; +import { Button, Menu, MenuButton, MenuList, Flex, Skeleton, chakra } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; import config from 'configs/app'; -import iconArrow from 'icons/arrows/east-mini.svg'; import useIsAccountActionAllowed from 'lib/hooks/useIsAccountActionAllowed'; import * as mixpanel from 'lib/mixpanel/index'; import getQueryParamString from 'lib/router/getQueryParamString'; +import IconSvg from 'ui/shared/IconSvg'; import PrivateTagMenuItem from './items/PrivateTagMenuItem'; import PublicTagMenuItem from './items/PublicTagMenuItem'; @@ -44,7 +44,7 @@ const AccountActionsMenu = ({ isLoading, className }: Props) => { > More - + diff --git a/ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx b/ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx index 4450dcd2b1..941434c390 100644 --- a/ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx +++ b/ui/shared/AccountActionsMenu/items/PrivateTagMenuItem.tsx @@ -1,4 +1,4 @@ -import { MenuItem, Icon, chakra, useDisclosure } from '@chakra-ui/react'; +import { MenuItem, chakra, useDisclosure } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; @@ -6,11 +6,11 @@ import React from 'react'; import type { Address } from 'types/api/address'; import type { Transaction } from 'types/api/transaction'; -import iconPrivateTags from 'icons/privattags.svg'; import { getResourceKey } from 'lib/api/useApiQuery'; import getPageType from 'lib/mixpanel/getPageType'; import AddressModal from 'ui/privateTags/AddressModal/AddressModal'; import TransactionModal from 'ui/privateTags/TransactionModal/TransactionModal'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { className?: string; @@ -61,7 +61,7 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick, type = 'address' } return ( <> - + Add private tag { type === 'tx' ? diff --git a/ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx b/ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx index 2f3c0c0e32..25de85da97 100644 --- a/ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx +++ b/ui/shared/AccountActionsMenu/items/PublicTagMenuItem.tsx @@ -1,8 +1,8 @@ -import { MenuItem, Icon, chakra } from '@chakra-ui/react'; +import { MenuItem, chakra } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; -import iconPublicTags from 'icons/publictags.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { className?: string; @@ -23,7 +23,7 @@ const PublicTagMenuItem = ({ className, hash, onBeforeClick }: Props) => { return ( - + Add public tag ); diff --git a/ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx b/ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx index 06950e9e82..b7e5f3d337 100644 --- a/ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx +++ b/ui/shared/AccountActionsMenu/items/TokenInfoMenuItem.tsx @@ -1,15 +1,15 @@ -import { MenuItem, Icon, chakra, useDisclosure } from '@chakra-ui/react'; +import { MenuItem, chakra, useDisclosure } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import React from 'react'; import type { Route } from 'nextjs-routes'; import config from 'configs/app'; -import iconEdit from 'icons/edit.svg'; import useApiQuery from 'lib/api/useApiQuery'; import useHasAccount from 'lib/hooks/useHasAccount'; import { PAGE_TYPE_DICT } from 'lib/mixpanel/getPageType'; import AddressVerificationModal from 'ui/addressVerification/AddressVerificationModal'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { className?: string; @@ -61,7 +61,7 @@ const TokenInfoMenuItem = ({ className, hash, onBeforeClick }: Props) => { router.push({ pathname: '/account/verified-addresses' }); }, [ router ]); - const icon = ; + const icon = ; const content = (() => { if (!verifiedAddressesQuery.data?.verifiedAddresses.find(({ contractAddress }) => contractAddress.toLowerCase() === hash.toLowerCase())) { diff --git a/ui/shared/AdditionalInfoButton.tsx b/ui/shared/AdditionalInfoButton.tsx index 39352cd502..5b1002bfeb 100644 --- a/ui/shared/AdditionalInfoButton.tsx +++ b/ui/shared/AdditionalInfoButton.tsx @@ -1,5 +1,4 @@ import { - Icon, useColorModeValue, chakra, Button, @@ -7,7 +6,7 @@ import { } from '@chakra-ui/react'; import React from 'react'; -import infoIcon from 'icons/info.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { isOpen?: boolean; @@ -39,8 +38,8 @@ const AdditionalInfoButton = ({ isOpen, onClick, className, isLoading }: Props, cursor="pointer" flexShrink={ 0 } > - { return ( - + diff --git a/ui/shared/AppError/AppErrorIcon.tsx b/ui/shared/AppError/AppErrorIcon.tsx index 35f6afe3ca..55b69cd1d1 100644 --- a/ui/shared/AppError/AppErrorIcon.tsx +++ b/ui/shared/AppError/AppErrorIcon.tsx @@ -1,16 +1,13 @@ -import { Icon } from '@chakra-ui/react'; import React from 'react'; -import icon404 from 'icons/error-pages/404.svg'; -import icon422 from 'icons/error-pages/422.svg'; -import icon429 from 'icons/error-pages/429.svg'; -import icon500 from 'icons/error-pages/500.svg'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; -const ICONS: Record> > = { - '404': icon404, - '422': icon422, - '429': icon429, - '500': icon500, +const ICONS: Record = { + '404': 'error-pages/404', + '422': 'error-pages/422', + '429': 'error-pages/429', + '500': 'error-pages/500', }; interface Props { @@ -18,7 +15,7 @@ interface Props { } const AppErrorIcon = ({ statusCode }: Props) => { - return ; + return ; }; export default AppErrorIcon; diff --git a/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_status-code-500-1.png b/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_status-code-500-1.png index 672c762dd0..d2ad3ad613 100644 Binary files a/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_status-code-500-1.png and b/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_status-code-500-1.png differ diff --git a/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_too-many-requests-mobile-1.png b/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_too-many-requests-mobile-1.png index 975ef10adc..26cb7b61cc 100644 Binary files a/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_too-many-requests-mobile-1.png and b/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_too-many-requests-mobile-1.png differ diff --git a/ui/shared/AppError/custom/AppErrorInvalidTxHash.tsx b/ui/shared/AppError/custom/AppErrorInvalidTxHash.tsx index 48b5f28841..a6da98596f 100644 --- a/ui/shared/AppError/custom/AppErrorInvalidTxHash.tsx +++ b/ui/shared/AppError/custom/AppErrorInvalidTxHash.tsx @@ -1,8 +1,8 @@ /* eslint-disable max-len */ -import { Box, OrderedList, ListItem, Icon, useColorModeValue, Flex } from '@chakra-ui/react'; +import { Box, OrderedList, ListItem, useColorModeValue, Flex } from '@chakra-ui/react'; import React from 'react'; -import txIcon from 'icons/transactions.svg'; +import IconSvg from 'ui/shared/IconSvg'; import AppErrorTitle from '../AppErrorTitle'; @@ -18,7 +18,7 @@ const AppErrorInvalidTxHash = () => { <> - + diff --git a/ui/shared/ClearButton.tsx b/ui/shared/ClearButton.tsx index 614b3620a4..f2a12592b0 100644 --- a/ui/shared/ClearButton.tsx +++ b/ui/shared/ClearButton.tsx @@ -1,7 +1,7 @@ -import { chakra, Icon, IconButton, useColorModeValue } from '@chakra-ui/react'; +import { chakra, IconButton, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; -import errorIcon from 'icons/status/error.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { onClick: (e: React.SyntheticEvent) => void; @@ -21,7 +21,7 @@ const ClearButton = ({ onClick, isDisabled, className }: Props) => { aria-label="Clear input" title="Clear input" boxSize={ 6 } - icon={ } + icon={ } size="sm" onClick={ onClick } /> diff --git a/ui/shared/CopyToClipboard.tsx b/ui/shared/CopyToClipboard.tsx index 00359599c0..be20647990 100644 --- a/ui/shared/CopyToClipboard.tsx +++ b/ui/shared/CopyToClipboard.tsx @@ -1,7 +1,7 @@ import { IconButton, Tooltip, useClipboard, chakra, useDisclosure, Skeleton } from '@chakra-ui/react'; import React, { useEffect, useState } from 'react'; -import CopyIcon from 'icons/copy.svg'; +import IconSvg from 'ui/shared/IconSvg'; export interface Props { text: string; @@ -31,7 +31,7 @@ const CopyToClipboard = ({ text, className, isLoading }: Props) => { } + icon={ } w="20px" h="20px" color="gray.400" diff --git a/ui/shared/EmptySearchResult.tsx b/ui/shared/EmptySearchResult.tsx index 5e7ca7d900..9c5b7b3720 100644 --- a/ui/shared/EmptySearchResult.tsx +++ b/ui/shared/EmptySearchResult.tsx @@ -1,7 +1,7 @@ -import { Box, Heading, Icon, Text } from '@chakra-ui/react'; +import { Box, Heading, Text } from '@chakra-ui/react'; import React from 'react'; -import emptyIcon from 'icons/empty_search_result.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { text: string; @@ -14,8 +14,8 @@ const EmptySearchResult = ({ text }: Props) => { flexDirection="column" alignItems="center" > - diff --git a/ui/home/StatsGasPrices.tsx b/ui/shared/GasInfoTooltipContent/GasInfoTooltipContent.tsx similarity index 57% rename from ui/home/StatsGasPrices.tsx rename to ui/shared/GasInfoTooltipContent/GasInfoTooltipContent.tsx index 103cb6b79e..952d65438b 100644 --- a/ui/home/StatsGasPrices.tsx +++ b/ui/shared/GasInfoTooltipContent/GasInfoTooltipContent.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { GasPrices } from 'types/api/stats'; -const StatsGasPrices = ({ gasPrices }: {gasPrices: GasPrices}) => { +const GasInfoTooltipContent = ({ gasPrices }: {gasPrices: GasPrices}) => { const nameStyleProps = { color: useColorModeValue('blue.100', 'blue.600'), }; @@ -11,13 +11,13 @@ const StatsGasPrices = ({ gasPrices }: {gasPrices: GasPrices}) => { return ( Slow - { `${ gasPrices.slow } Gwei` } + { gasPrices.slow !== null ? `${ gasPrices.slow } Gwei` : 'N/A' } Average - { `${ gasPrices.average } Gwei` } + { gasPrices.average !== null ? `${ gasPrices.average } Gwei` : 'N/A' } Fast - { `${ gasPrices.fast } Gwei` } + { gasPrices.fast !== null ? `${ gasPrices.fast } Gwei` : 'N/A' } ); }; -export default StatsGasPrices; +export default React.memo(GasInfoTooltipContent); diff --git a/ui/shared/Hint.tsx b/ui/shared/Hint.tsx index 7a4908f8c6..1e4da78755 100644 --- a/ui/shared/Hint.tsx +++ b/ui/shared/Hint.tsx @@ -2,7 +2,7 @@ import type { TooltipProps } from '@chakra-ui/react'; import { chakra, IconButton, Tooltip, useDisclosure, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import InfoIcon from 'icons/info.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { label: string | React.ReactNode; @@ -21,7 +21,7 @@ const Hint = ({ label, className, tooltipProps, isLoading }: Props) => { }, [ onToggle ]); if (isLoading) { - return ; + return ; } return ( @@ -35,7 +35,7 @@ const Hint = ({ label, className, tooltipProps, isLoading }: Props) => { } + icon={ } boxSize={ 5 } variant="simple" display="inline-block" diff --git a/ui/shared/IconSvg.tsx b/ui/shared/IconSvg.tsx new file mode 100644 index 0000000000..fb30c5e8cd --- /dev/null +++ b/ui/shared/IconSvg.tsx @@ -0,0 +1,25 @@ +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Skeleton, chakra } from '@chakra-ui/react'; +import { type IconName } from 'public/icons/name'; +import React from 'react'; + +export const href = '/icons/sprite.svg'; + +export { IconName }; + +interface Props extends HTMLChakraProps<'div'> { + name: IconName; + isLoading?: boolean; +} + +const IconSvg = ({ name, isLoading, ...props }: Props) => { + return ( + + + + + + ); +}; + +export default IconSvg; diff --git a/ui/shared/LinkExternal.tsx b/ui/shared/LinkExternal.tsx index 64401dab4a..cae2427170 100644 --- a/ui/shared/LinkExternal.tsx +++ b/ui/shared/LinkExternal.tsx @@ -1,8 +1,8 @@ import type { ChakraProps } from '@chakra-ui/react'; -import { Link, Icon, chakra, Box, Skeleton, useColorModeValue } from '@chakra-ui/react'; +import { Link, chakra, Box, Skeleton, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; -import arrowIcon from 'icons/arrows/north-east.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { href: string; @@ -59,7 +59,7 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props) return ( { children } - + ); }; diff --git a/ui/shared/LinkInternal.tsx b/ui/shared/LinkInternal.tsx index 09a9764e62..b46b369096 100644 --- a/ui/shared/LinkInternal.tsx +++ b/ui/shared/LinkInternal.tsx @@ -5,7 +5,6 @@ import NextLink from 'next/link'; import type { LegacyRef } from 'react'; import React from 'react'; -// NOTE! use this component only for links to pages that are completely implemented in new UI const LinkInternal = ({ isLoading, ...props }: LinkProps & { isLoading?: boolean }, ref: LegacyRef) => { if (isLoading) { return { props.children }; diff --git a/ui/shared/NetworkAddToWallet.tsx b/ui/shared/NetworkAddToWallet.tsx index 508e524e6c..993e10a9fc 100644 --- a/ui/shared/NetworkAddToWallet.tsx +++ b/ui/shared/NetworkAddToWallet.tsx @@ -1,4 +1,4 @@ -import { Button, Icon } from '@chakra-ui/react'; +import { Button } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; @@ -7,6 +7,7 @@ import * as mixpanel from 'lib/mixpanel/index'; import useAddOrSwitchChain from 'lib/web3/useAddOrSwitchChain'; import useProvider from 'lib/web3/useProvider'; import { WALLETS_INFO } from 'lib/web3/wallets'; +import IconSvg from 'ui/shared/IconSvg'; const feature = config.features.web3Wallet; @@ -55,7 +56,7 @@ const NetworkAddToWallet = () => { return ( ); diff --git a/ui/shared/NetworkExplorers.tsx b/ui/shared/NetworkExplorers.tsx index 1475375511..d0cefc98d0 100644 --- a/ui/shared/NetworkExplorers.tsx +++ b/ui/shared/NetworkExplorers.tsx @@ -1,12 +1,11 @@ -import { Flex, Button, Icon, chakra, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure } from '@chakra-ui/react'; +import { Flex, Button, chakra, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure } from '@chakra-ui/react'; import React from 'react'; import type { NetworkExplorer as TNetworkExplorer } from 'types/networks'; import config from 'configs/app'; -import arrowIcon from 'icons/arrows/east-mini.svg'; -import explorerIcon from 'icons/explorer.svg'; import stripTrailingSlash from 'lib/stripTrailingSlash'; +import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; interface Props { @@ -46,8 +45,8 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => { h="32px" flexShrink={ 0 } > - - + + diff --git a/ui/shared/Page/PageTitle.tsx b/ui/shared/Page/PageTitle.tsx index 9167e7e87e..d531921cc4 100644 --- a/ui/shared/Page/PageTitle.tsx +++ b/ui/shared/Page/PageTitle.tsx @@ -1,10 +1,10 @@ -import { Heading, Flex, Tooltip, Icon, Link, chakra, Skeleton, useDisclosure } from '@chakra-ui/react'; +import { Heading, Flex, Tooltip, Link, chakra, Skeleton, useDisclosure } from '@chakra-ui/react'; import _debounce from 'lodash/debounce'; import React from 'react'; -import eastArrowIcon from 'icons/arrows/east.svg'; import useIsMobile from 'lib/hooks/useIsMobile'; import TextAd from 'ui/shared/ad/TextAd'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; type BackLinkProp = { label: string; url: string } | { label: string; onClick: () => void }; @@ -32,7 +32,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => { return ; } - const icon = ; + const icon = ; if ('url' in props) { return ( diff --git a/ui/shared/Page/specs/DefaultView.tsx b/ui/shared/Page/specs/DefaultView.tsx index 23db4033fd..0c924f55e0 100644 --- a/ui/shared/Page/specs/DefaultView.tsx +++ b/ui/shared/Page/specs/DefaultView.tsx @@ -1,13 +1,12 @@ -import { Icon } from '@chakra-ui/react'; import React from 'react'; import type { TokenInfo } from 'types/api/token'; -import iconVerifiedToken from 'icons/verified_token.svg'; import * as addressMock from 'mocks/address/address'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import EntityTags from 'ui/shared/EntityTags'; +import IconSvg from 'ui/shared/IconSvg'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import PageTitle from '../PageTitle'; @@ -33,7 +32,7 @@ const DefaultView = () => { const contentAfter = ( <> - + { const contentAfter = ( <> - + } + icon={ } h={ 6 } borderRadius="sm" variant="subtle" @@ -48,7 +48,7 @@ const PrevNext = ({ className, onClick, prevLabel, nextLabel, isPrevDisabled, is } + icon={ } h={ 6 } borderRadius="sm" variant="subtle" diff --git a/ui/shared/ResetIconButton.tsx b/ui/shared/ResetIconButton.tsx new file mode 100644 index 0000000000..0b80918219 --- /dev/null +++ b/ui/shared/ResetIconButton.tsx @@ -0,0 +1,31 @@ +import { Tooltip, Flex, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +type Props = { + onClick: () => void; +} + +const ResetIconButton = ({ onClick }: Props) => { + const resetTokenIconColor = useColorModeValue('blue.600', 'blue.300'); + const resetTokenIconHoverColor = useColorModeValue('blue.400', 'blue.200'); + + return ( + + + + + + ); +}; + +export default ResetIconButton; diff --git a/ui/shared/TableItemActionButtons.tsx b/ui/shared/TableItemActionButtons.tsx index 869e177021..76e5c4f327 100644 --- a/ui/shared/TableItemActionButtons.tsx +++ b/ui/shared/TableItemActionButtons.tsx @@ -1,9 +1,8 @@ import { Tooltip, IconButton, HStack, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import DeleteIcon from 'icons/delete.svg'; -import EditIcon from 'icons/edit.svg'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; +import IconSvg from 'ui/shared/IconSvg'; type Props = { onEditClick: () => void; @@ -31,7 +30,7 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props variant="simple" boxSize={ 5 } onClick={ onEditClick } - icon={ } + icon={ } onFocusCapture={ onFocusCapture } display="inline-block" flexShrink={ 0 } @@ -44,7 +43,7 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props variant="simple" boxSize={ 5 } onClick={ onDeleteClick } - icon={ } + icon={ } onFocusCapture={ onFocusCapture } display="inline-block" flexShrink={ 0 } diff --git a/ui/shared/TokenLogoPlaceholder.tsx b/ui/shared/TokenLogoPlaceholder.tsx index 5f50d27b7a..343df209f2 100644 --- a/ui/shared/TokenLogoPlaceholder.tsx +++ b/ui/shared/TokenLogoPlaceholder.tsx @@ -1,20 +1,20 @@ -import { chakra, Icon, useColorModeValue } from '@chakra-ui/react'; +import { chakra, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; -import tokenPlaceholderIcon from 'icons/token-placeholder.svg'; +import IconSvg from 'ui/shared/IconSvg'; const TokenLogoPlaceholder = ({ className }: { className?: string }) => { const bgColor = useColorModeValue('gray.200', 'gray.600'); const color = useColorModeValue('gray.400', 'gray.200'); return ( - ) } Type - + onChange={ onTypeFilterChange } defaultValue={ defaultTypeFilters } nftOnly={ false }/> ); }; diff --git a/ui/shared/TokenTransfer/TokenTransferListItem.tsx b/ui/shared/TokenTransfer/TokenTransferListItem.tsx index f354e03c39..0d63e07b51 100644 --- a/ui/shared/TokenTransfer/TokenTransferListItem.tsx +++ b/ui/shared/TokenTransfer/TokenTransferListItem.tsx @@ -3,15 +3,14 @@ import React from 'react'; import type { TokenTransfer } from 'types/api/tokenTransfer'; -import eastArrowIcon from 'icons/arrows/east.svg'; import getCurrencyValue from 'lib/getCurrencyValue'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import IconSvg from 'ui/shared/IconSvg'; import InOutTag from 'ui/shared/InOutTag'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import { getTokenTransferTypeText } from 'ui/shared/TokenTransfer/helpers'; @@ -100,7 +99,7 @@ const TokenTransferListItem = ({ flexShrink={ 0 } /> ) : - + } { boxSize={ `${ size }px` } borderRadius="full" overflow="hidden" - fallback={ isImageLoadError || !data?.avatar ? : undefined } + fallback={ isImageLoadError || !data?.avatar ? : undefined } onError={ handleImageLoadError } /> ); diff --git a/ui/shared/Web3ModalProvider.tsx b/ui/shared/Web3ModalProvider.tsx index 3a7a48579d..24db96e0f0 100644 --- a/ui/shared/Web3ModalProvider.tsx +++ b/ui/shared/Web3ModalProvider.tsx @@ -1,12 +1,14 @@ -import { useColorModeValue, useToken } from '@chakra-ui/react'; +import { useColorMode } from '@chakra-ui/react'; import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc'; -import { EthereumClient, w3mConnectors } from '@web3modal/ethereum'; -import { Web3Modal } from '@web3modal/react'; +import { createWeb3Modal, useWeb3ModalTheme, defaultWagmiConfig } from '@web3modal/wagmi/react'; import React from 'react'; import type { Chain } from 'wagmi'; -import { configureChains, createConfig, WagmiConfig } from 'wagmi'; +import { configureChains, WagmiConfig } from 'wagmi'; import config from 'configs/app'; +import colors from 'theme/foundations/colors'; +import { BODY_TYPEFACE } from 'theme/foundations/typography'; +import zIndices from 'theme/foundations/zIndices'; const feature = config.features.blockchainInteraction; @@ -41,58 +43,71 @@ const getConfig = () => { }, }; - const chains = [ currentChain ]; - - const { publicClient } = configureChains(chains, [ - jsonRpcProvider({ - rpc: () => ({ - http: config.chain.rpcUrl || '', + const { chains } = configureChains( + [ currentChain ], + [ + jsonRpcProvider({ + rpc: () => ({ + http: config.chain.rpcUrl || '', + }), }), - }), - ]); - const wagmiConfig = createConfig({ - autoConnect: true, - connectors: w3mConnectors({ projectId: feature.walletConnect.projectId, chains }), - publicClient, + ], + ); + + const wagmiConfig = defaultWagmiConfig({ + chains, + projectId: feature.walletConnect.projectId, + }); + + createWeb3Modal({ + wagmiConfig, + projectId: feature.walletConnect.projectId, + chains, + themeVariables: { + '--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`, + '--w3m-accent': colors.blue[600], + '--w3m-border-radius-master': '2px', + '--w3m-z-index': zIndices.modal, + }, }); - const ethereumClient = new EthereumClient(wagmiConfig, chains); - return { wagmiConfig, ethereumClient }; + return { wagmiConfig }; } catch (error) { - return { wagmiConfig: undefined, ethereumClient: undefined }; + return { }; } }; -const { wagmiConfig, ethereumClient } = getConfig(); +const { wagmiConfig } = getConfig(); interface Props { children: React.ReactNode; fallback?: JSX.Element | (() => JSX.Element); } -const Web3ModalProvider = ({ children, fallback }: Props) => { - const modalZIndex = useToken('zIndices', 'modal'); - const web3ModalTheme = useColorModeValue('light', 'dark'); +const Fallback = ({ children, fallback }: Props) => { + return typeof fallback === 'function' ? fallback() : (fallback || <>{ children }); // eslint-disable-line react/jsx-no-useless-fragment +}; + +const Provider = ({ children, fallback }: Props) => { + const { colorMode } = useColorMode(); + const { setThemeMode } = useWeb3ModalTheme(); - if (!wagmiConfig || !ethereumClient || !feature.isEnabled) { - return typeof fallback === 'function' ? fallback() : (fallback || null); + React.useEffect(() => { + setThemeMode(colorMode); + }, [ colorMode, setThemeMode ]); + + // not really necessary, but we have to make typescript happy + if (!wagmiConfig || !feature.isEnabled) { + return { children }; } return ( - <> - - { children } - - - + + { children } + ); }; +const Web3ModalProvider = wagmiConfig && feature.isEnabled ? Provider : Fallback; + export default Web3ModalProvider; diff --git a/ui/shared/address/AddressAddToWallet.tsx b/ui/shared/address/AddressAddToWallet.tsx index a9c640b300..144e21101f 100644 --- a/ui/shared/address/AddressAddToWallet.tsx +++ b/ui/shared/address/AddressAddToWallet.tsx @@ -1,4 +1,4 @@ -import { Box, chakra, Icon, IconButton, Skeleton, Tooltip } from '@chakra-ui/react'; +import { Box, chakra, IconButton, Skeleton, Tooltip } from '@chakra-ui/react'; import React from 'react'; import type { TokenInfo } from 'types/api/token'; @@ -9,6 +9,7 @@ import * as mixpanel from 'lib/mixpanel/index'; import useAddOrSwitchChain from 'lib/web3/useAddOrSwitchChain'; import useProvider from 'lib/web3/useProvider'; import { WALLETS_INFO } from 'lib/web3/wallets'; +import IconSvg from 'ui/shared/IconSvg'; const feature = config.features.web3Wallet; @@ -97,7 +98,7 @@ const AddressAddToWallet = ({ className, token, isLoading, variant = 'icon', ico size="sm" px="6px" onClick={ handleClick } - icon={ } + icon={ } flexShrink={ 0 } /> @@ -106,8 +107,8 @@ const AddressAddToWallet = ({ className, token, isLoading, variant = 'icon', ico return ( - - + + ); diff --git a/ui/shared/chakra/Icon.tsx b/ui/shared/chakra/Icon.tsx deleted file mode 100644 index 0e270a5d6a..0000000000 --- a/ui/shared/chakra/Icon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Skeleton, Icon as ChakraIcon } from '@chakra-ui/react'; -import type { IconProps, As } from '@chakra-ui/react'; -import React from 'react'; - -interface Props extends IconProps { - isLoading?: boolean; - as: As; -} - -const Icon = ({ isLoading, ...props }: Props, ref: React.LegacyRef) => { - return ( - - - - ); -}; - -export default React.memo(React.forwardRef(Icon)); diff --git a/ui/shared/chart/ChartWidget.tsx b/ui/shared/chart/ChartWidget.tsx index 93d1e5c0ae..b613b8f7a4 100644 --- a/ui/shared/chart/ChartWidget.tsx +++ b/ui/shared/chart/ChartWidget.tsx @@ -3,7 +3,6 @@ import { Center, chakra, Flex, - Icon, IconButton, Link, Menu, MenuButton, @@ -20,14 +19,10 @@ import React, { useRef, useCallback, useState } from 'react'; import type { TimeChartItem } from './types'; -import svgFileIcon from 'icons/files/csv.svg'; -import imageIcon from 'icons/files/image.svg'; -import repeatArrowIcon from 'icons/repeat_arrow.svg'; -import scopeIcon from 'icons/scope.svg'; -import dotsIcon from 'icons/vertical_dots.svg'; import dayjs from 'lib/date/dayjs'; import { apos } from 'lib/html-entities'; import saveAsCSV from 'lib/saveAsCSV'; +import IconSvg from 'ui/shared/IconSvg'; import ChartWidgetGraph from './ChartWidgetGraph'; import FullscreenChartModal from './FullscreenChartModal'; @@ -202,7 +197,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, size="sm" variant="outline" onClick={ handleZoomResetClick } - icon={ } + icon={ } /> @@ -212,7 +207,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, } + icon={ } colorScheme="gray" variant="ghost" as={ IconButton } @@ -228,7 +223,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, alignItems="center" onClick={ showChartFullscreen } > - + View fullscreen @@ -237,7 +232,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, alignItems="center" onClick={ handleFileSaveClick } > - + Save as PNG @@ -246,7 +241,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, alignItems="center" onClick={ handleSVGSavingClick } > - + Save as CSV diff --git a/ui/shared/chart/FullscreenChartModal.tsx b/ui/shared/chart/FullscreenChartModal.tsx index 8752e11ce7..d7a3b1c692 100644 --- a/ui/shared/chart/FullscreenChartModal.tsx +++ b/ui/shared/chart/FullscreenChartModal.tsx @@ -1,9 +1,9 @@ -import { Box, Button, Grid, Heading, Icon, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, Text } from '@chakra-ui/react'; +import { Box, Button, Grid, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, Text } from '@chakra-ui/react'; import React, { useCallback } from 'react'; import type { TimeChartItem } from './types'; -import repeatArrow from 'icons/repeat_arrow.svg'; +import IconSvg from 'ui/shared/IconSvg'; import ChartWidgetGraph from './ChartWidgetGraph'; @@ -71,7 +71,7 @@ const FullscreenChartModal = ({ { !isZoomResetInitial && ( + ); + } + + return ( + + ); +}; + +type RadioButtonGroupProps = { + onChange: (value: T) => void; + name: string; + defaultValue: string; + options: Array<{ value: T } & RadioItemProps>; +} + +const RadioButtonGroup = ({ onChange, name, defaultValue, options }: RadioButtonGroupProps) => { + const { getRootProps, getRadioProps } = useRadioGroup({ name, defaultValue, onChange }); + + const group = getRootProps(); + + return ( + + { options.map((option) => { + const props = getRadioProps({ value: option.value }); + return ; + }) } + + ); +}; + +export default RadioButtonGroup; diff --git a/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png b/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png new file mode 100644 index 0000000000..212b0b3e89 Binary files /dev/null and b/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png differ diff --git a/ui/shared/radioButtonGroup/specs/RadioButtonGroupTest.tsx b/ui/shared/radioButtonGroup/specs/RadioButtonGroupTest.tsx new file mode 100644 index 0000000000..c601cefdf4 --- /dev/null +++ b/ui/shared/radioButtonGroup/specs/RadioButtonGroupTest.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import RadioButtonGroup from '../RadioButtonGroup'; + +type Test = 'v1' | 'v2' | 'v3'; + +const RadioButtonGroupTest = () => { + return ( + + // eslint-disable-next-line react/jsx-no-bind + onChange={ () => {} } + defaultValue="v1" + name="test" + options={ [ + { value: 'v1', title: 'test option 1', icon: 'clock', onlyIcon: false }, + { value: 'v2', title: 'test 2', onlyIcon: false }, + { value: 'v2', title: 'test 2', icon: 'clock', onlyIcon: true }, + ] } + /> + ); +}; + +export default RadioButtonGroupTest; diff --git a/ui/shared/sort/SortButton.tsx b/ui/shared/sort/SortButton.tsx index f4525b79d7..903bb1fcec 100644 --- a/ui/shared/sort/SortButton.tsx +++ b/ui/shared/sort/SortButton.tsx @@ -1,7 +1,7 @@ -import { Icon, IconButton, chakra, Skeleton } from '@chakra-ui/react'; +import { IconButton, chakra, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import upDownArrow from 'icons/arrows/up-down.svg'; +import IconSvg from 'ui/shared/IconSvg'; type Props = { onClick: () => void; @@ -17,7 +17,7 @@ const SortButton = ({ onClick, isActive, className, isLoading }: Props) => { return ( } + icon={ } aria-label="sort" size="sm" variant="outline" diff --git a/ui/shared/sort/getSortParamsFromValue.ts b/ui/shared/sort/getSortParamsFromValue.ts new file mode 100644 index 0000000000..a4da57565f --- /dev/null +++ b/ui/shared/sort/getSortParamsFromValue.ts @@ -0,0 +1,8 @@ +export default function getSortParamsFromValue(val?: SortValue) { + if (!val) { + return undefined; + } + + const sortingChunks = val.split('-') as [ SortField, SortOrder ]; + return { sort: sortingChunks[0], order: sortingChunks[1] }; +} diff --git a/ui/shared/sort/getSortValueFromQuery.ts b/ui/shared/sort/getSortValueFromQuery.ts new file mode 100644 index 0000000000..9e8f823c60 --- /dev/null +++ b/ui/shared/sort/getSortValueFromQuery.ts @@ -0,0 +1,14 @@ +import type { Query } from 'nextjs-routes'; + +import type { Option } from 'ui/shared/sort/Sort'; + +export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { + if (!query.sort || !query.order) { + return undefined; + } + + const str = query.sort + '-' + query.order; + if (sortOptions.map(option => option.id).includes(str as SortValue)) { + return str as SortValue; + } +} diff --git a/ui/shared/statusTag/StatusTag.tsx b/ui/shared/statusTag/StatusTag.tsx index 1075b1dfe7..8a9d8a3519 100644 --- a/ui/shared/statusTag/StatusTag.tsx +++ b/ui/shared/statusTag/StatusTag.tsx @@ -1,10 +1,9 @@ -import { TagLabel, TagLeftIcon, Tooltip } from '@chakra-ui/react'; +import { TagLabel, Tooltip } from '@chakra-ui/react'; import React from 'react'; -import errorIcon from 'icons/status/error.svg'; -import pendingIcon from 'icons/status/pending.svg'; -import successIcon from 'icons/status/success.svg'; import Tag from 'ui/shared/chakra/Tag'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; export type StatusTagType = 'ok' | 'error' | 'pending'; @@ -16,20 +15,20 @@ export interface Props { } const StatusTag = ({ type, text, errorText, isLoading }: Props) => { - let icon; + let icon: IconName; let colorScheme; switch (type) { case 'ok': - icon = successIcon; + icon = 'status/success'; colorScheme = 'green'; break; case 'error': - icon = errorIcon; + icon = 'status/error'; colorScheme = 'red'; break; case 'pending': - icon = pendingIcon; + icon = 'status/pending'; // FIXME: it's not gray on mockups // need to implement new color scheme or redefine colors here colorScheme = 'gray'; @@ -39,7 +38,7 @@ const StatusTag = ({ type, text, errorText, isLoading }: Props) => { return ( - + { text } diff --git a/ui/shared/tx/TxFeeStability.tsx b/ui/shared/tx/TxFeeStability.tsx index 5a3279e12f..d1c10c0d9a 100644 --- a/ui/shared/tx/TxFeeStability.tsx +++ b/ui/shared/tx/TxFeeStability.tsx @@ -27,7 +27,7 @@ const TxFeeStability = ({ data, isLoading, hideUsd, accuracy, className }: Props return ( { valueStr } - { valueStr !== '0' && } + { valueStr !== '0' && } { usd && !hideUsd && (${ usd }) } ); diff --git a/ui/shared/verificationSteps/VerificationStep.tsx b/ui/shared/verificationSteps/VerificationStep.tsx index c3b50a9819..38d78462fb 100644 --- a/ui/shared/verificationSteps/VerificationStep.tsx +++ b/ui/shared/verificationSteps/VerificationStep.tsx @@ -1,11 +1,9 @@ -import { Text, Icon, HStack } from '@chakra-ui/react'; +import { Text, HStack } from '@chakra-ui/react'; import React from 'react'; import type { Step } from './types'; -import arrowIcon from 'icons/arrows/east.svg'; -import finalizedIcon from 'icons/finalized.svg'; -import unfinalizedIcon from 'icons/unfinalized.svg'; +import IconSvg from 'ui/shared/IconSvg'; type Props = { step: Step; @@ -18,9 +16,9 @@ const VerificationStep = ({ step, isLast, isPassed }: Props) => { return ( - + { typeof step === 'string' ? step : step.content } - { !isLast && } + { !isLast && } ); }; diff --git a/ui/snippets/footer/Footer.tsx b/ui/snippets/footer/Footer.tsx index 475f3005d8..404dbb9b3a 100644 --- a/ui/snippets/footer/Footer.tsx +++ b/ui/snippets/footer/Footer.tsx @@ -1,3 +1,4 @@ +import type { GridProps } from '@chakra-ui/react'; import { Box, Grid, Flex, Text, Link, VStack, Skeleton } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import React from 'react'; @@ -5,16 +6,11 @@ import React from 'react'; import type { CustomLinksGroup } from 'types/footerLinks'; import config from 'configs/app'; -import discordIcon from 'icons/social/discord.svg'; -import gitIcon from 'icons/social/git.svg'; -import iconTelegram from 'icons/social/telegram_filled.svg'; -import twitterIcon from 'icons/social/tweet.svg'; import type { ResourceError } from 'lib/api/resources'; import useApiQuery from 'lib/api/useApiQuery'; import useFetch from 'lib/hooks/useFetch'; import NetworkAddToWallet from 'ui/shared/NetworkAddToWallet'; -import ColorModeToggler from '../header/ColorModeToggler'; import FooterLinkItem from './FooterLinkItem'; import IntTxsIndexingStatus from './IntTxsIndexingStatus'; import getApiVersionUrl from './utils/getApiVersionUrl'; @@ -34,25 +30,25 @@ const Footer = () => { const apiVersionUrl = getApiVersionUrl(backendVersionData?.backend_version); const BLOCKSCOUT_LINKS = [ { - icon: gitIcon, + icon: 'social/git' as const, iconSize: '18px', text: 'Github', url: 'https://github.com/aurora-is-near', }, { - icon: twitterIcon, + icon: 'social/tweet' as const, iconSize: '18px', text: 'Twitter', url: 'https://twitter.com/auroraisnear', }, { - icon: discordIcon, + icon: 'social/discord' as const, iconSize: '24px', text: 'Discord', url: 'https://discord.gg/dEFJBz8HQV', }, { - icon: iconTelegram, + icon: 'social/telegram_filled' as const, iconSize: '20px', text: 'Telegram', url: 'https://t.me/auroraisnear', @@ -73,41 +69,43 @@ const Footer = () => { const fetch = useFetch(); - const { isPending, data: linksData } = useQuery, Array>({ + const { isPlaceholderData, data: linksData } = useQuery, Array>({ queryKey: [ 'footer-links' ], queryFn: async() => fetch(config.UI.footer.links || '', undefined, { resource: 'footer-links' }), enabled: Boolean(config.UI.footer.links), staleTime: Infinity, + placeholderData: [], }); - const colNum = Math.min(linksData?.length || Infinity, MAX_LINKS_COLUMNS) + 1; + const colNum = isPlaceholderData ? 1 : Math.min(linksData?.length || Infinity, MAX_LINKS_COLUMNS) + 1; - return ( - - - - - { !config.UI.indexingAlert.intTxs.isHidden && } - - - - blockscout.com - - - Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks. + const renderNetworkInfo = React.useCallback((gridArea?: GridProps['gridArea']) => { + return ( + + { !config.UI.indexingAlert.intTxs.isHidden && } + + + ); + }, []); + + const renderProjectInfo = React.useCallback((gridArea?: GridProps['gridArea']) => { + return ( + + blockscout.com + + Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks. { apiVersionUrl && ( - Backend: { backendVersionData?.backend_version } + Backend: { backendVersionData?.backend_version } ) } { frontendLink && ( @@ -117,64 +115,93 @@ const Footer = () => { ) } - +
+ { renderNetworkInfo() } + { renderProjectInfo() } +
+ + + { + ([ + { title: 'Blockscout', links: BLOCKSCOUT_LINKS }, + ...(linksData || []), + ]) + .slice(0, colNum) + .map(linkGroup => ( + + { linkGroup.title } + + { linkGroup.links.map(link => ) } + + + )) + } + +
+ ); + } + + return ( + + + { renderNetworkInfo({ lg: 'network' }) } + { renderProjectInfo({ lg: 'info' }) } + + - - { config.UI.footer.links && Blockscout } - - { BLOCKSCOUT_LINKS.map(link => ) } - - - { config.UI.footer.links && isPending && ( - Array.from(Array(3)).map((i, index) => ( - - - - { Array.from(Array(5)).map((i, index) => ) } - - - )) - ) } - { config.UI.footer.links && linksData && ( - linksData.slice(0, MAX_LINKS_COLUMNS).map(linkGroup => ( - - { linkGroup.title } - - { linkGroup.links.map(link => ) } - - - )) - ) } + { BLOCKSCOUT_LINKS.map(link => ) } -
+ ); }; -export default Footer; +export default React.memo(Footer); diff --git a/ui/snippets/footer/FooterLinkItem.tsx b/ui/snippets/footer/FooterLinkItem.tsx index 6277a64aa2..9d334a42d0 100644 --- a/ui/snippets/footer/FooterLinkItem.tsx +++ b/ui/snippets/footer/FooterLinkItem.tsx @@ -1,19 +1,27 @@ -import { Center, Icon, Link } from '@chakra-ui/react'; +import { Center, Link, Skeleton } from '@chakra-ui/react'; import React from 'react'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + type Props = { - icon?: React.FC>; + icon?: IconName; iconSize?: string; text: string; url: string; + isLoading?: boolean; } -const FooterLinkItem = ({ icon, iconSize, text, url }: Props) => { +const FooterLinkItem = ({ icon, iconSize, text, url, isLoading }: Props) => { + if (isLoading) { + return { text }; + } + return ( { icon && (
- +
) } { text } diff --git a/ui/snippets/footer/IntTxsIndexingStatus.tsx b/ui/snippets/footer/IntTxsIndexingStatus.tsx index 3e0753b0bc..fbf533f968 100644 --- a/ui/snippets/footer/IntTxsIndexingStatus.tsx +++ b/ui/snippets/footer/IntTxsIndexingStatus.tsx @@ -1,15 +1,15 @@ -import { IconButton, Icon, Popover, PopoverTrigger, PopoverContent, PopoverBody, Flex, Text, useColorModeValue } from '@chakra-ui/react'; +import { IconButton, Popover, PopoverTrigger, PopoverContent, PopoverBody, Flex, Text, useColorModeValue } from '@chakra-ui/react'; import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; import type { SocketMessage } from 'lib/socket/types'; import type { IndexingStatus } from 'types/api/indexingStatus'; -import infoIcon from 'icons/info.svg'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import { apos, nbsp, ndash } from 'lib/html-entities'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; +import IconSvg from 'ui/shared/IconSvg'; const IntTxsIndexingStatus = () => { @@ -72,7 +72,7 @@ const IntTxsIndexingStatus = () => { } + icon={ } boxSize={ 6 } variant="simple" /> diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png deleted file mode 100644 index ff1d4ea7ac..0000000000 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-2-cols-base-view-dark-mode-mobile-1.png and /dev/null differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png deleted file mode 100644 index 53b597a4f1..0000000000 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-4-cols-mobile-dark-mode-1.png and /dev/null differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-max-cols-mobile-dark-mode-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-max-cols-mobile-dark-mode-1.png index be66be8940..0fc5ef3bc9 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-max-cols-mobile-dark-mode-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-max-cols-mobile-dark-mode-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png index 7a13dc53d1..ca21b198bd 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png index daece4eed5..8e7ccaa17f 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-base-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png index b8b1eaafb2..30756d332a 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_dark-color-mode_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-mobile-dark-mode-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-mobile-dark-mode-1.png index ca62e0602d..4ed0980dbd 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-mobile-dark-mode-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-mobile-dark-mode-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-screen-xl-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-screen-xl-1.png index c50e296c49..252ba0ea84 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-screen-xl-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-max-cols-screen-xl-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png index 713a28e678..a8b838f541 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png index c6eedcc4aa..aada6b053a 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-base-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png index e3d4d9ab64..5681129337 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_default_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-max-cols-mobile-dark-mode-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-max-cols-mobile-dark-mode-1.png index ce4f33dbcc..edab8c4966 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-max-cols-mobile-dark-mode-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-max-cols-mobile-dark-mode-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png index d8da957265..483b96df6c 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_with-custom-links-min-cols-base-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-base-view-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-base-view-dark-mode-mobile-1.png index 73725ebc78..ba074854d2 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-base-view-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-base-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png index cbd62b894d..c519578200 100644 Binary files a/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png and b/ui/snippets/footer/__screenshots__/Footer.pw.tsx_mobile_without-custom-links-with-indexing-alert-dark-mode-mobile-1.png differ diff --git a/ui/snippets/header/Burger.pw.tsx b/ui/snippets/header/Burger.pw.tsx index e996170126..4fc912b6de 100644 --- a/ui/snippets/header/Burger.pw.tsx +++ b/ui/snippets/header/Burger.pw.tsx @@ -50,7 +50,7 @@ test('base view', async({ mount, page }) => { { hooksConfig }, ); - await component.locator('svg[aria-label="Menu button"]').click(); + await component.locator('div[aria-label="Menu button"]').click(); await expect(page.locator('.chakra-modal__content-container')).toHaveScreenshot(); await page.locator('button[aria-label="Network menu"]').click(); @@ -80,7 +80,7 @@ test.describe('dark mode', () => { { hooksConfig }, ); - await component.locator('svg[aria-label="Menu button"]').click(); + await component.locator('div[aria-label="Menu button"]').click(); await expect(page).toHaveScreenshot(); await page.locator('button[aria-label="Network menu"]').click(); @@ -96,7 +96,7 @@ test('submenu', async({ mount, page }) => { { hooksConfig }, ); - await component.locator('svg[aria-label="Menu button"]').click(); + await component.locator('div[aria-label="Menu button"]').click(); await page.locator('div[aria-label="Blockchain link group"]').click(); await expect(page).toHaveScreenshot(); }); @@ -122,7 +122,7 @@ test.describe('auth', () => { { hooksConfig }, ); - await component.locator('svg[aria-label="Menu button"]').click(); + await component.locator('div[aria-label="Menu button"]').click(); await expect(page).toHaveScreenshot(); }); }); diff --git a/ui/snippets/header/Burger.tsx b/ui/snippets/header/Burger.tsx index 08f655b915..8d97220ec5 100644 --- a/ui/snippets/header/Burger.tsx +++ b/ui/snippets/header/Burger.tsx @@ -1,16 +1,19 @@ -import { Icon, Box, Flex, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useColorModeValue, useDisclosure } from '@chakra-ui/react'; +import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useColorModeValue, useDisclosure } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; -import burgerIcon from 'icons/burger.svg'; -import testnetIcon from 'icons/testnet.svg'; +import IconSvg from 'ui/shared/IconSvg'; import NavigationMobile from 'ui/snippets/navigation/NavigationMobile'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkMenuButton from 'ui/snippets/networkMenu/NetworkMenuButton'; import NetworkMenuContentMobile from 'ui/snippets/networkMenu/NetworkMenuContentMobile'; import useNetworkMenu from 'ui/snippets/networkMenu/useNetworkMenu'; -const Burger = () => { +interface Props { + isMarketplaceAppPage?: boolean; +} + +const Burger = ({ isMarketplaceAppPage }: Props) => { const iconColor = useColorModeValue('gray.600', 'white'); const { isOpen, onOpen, onClose } = useDisclosure(); const networkMenu = useNetworkMenu(); @@ -26,9 +29,9 @@ const Burger = () => { return ( <> - - + { - { config.chain.isTestnet && } + { config.chain.isTestnet && } { config.UI.sidebar.featuredNetworks ? ( @@ -57,7 +60,7 @@ const Burger = () => { { networkMenu.isOpen ? : - + } diff --git a/ui/snippets/header/ColorModeToggler.tsx b/ui/snippets/header/ColorModeToggler.tsx deleted file mode 100644 index 5ba097948c..0000000000 --- a/ui/snippets/header/ColorModeToggler.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import type { UseCheckboxProps } from '@chakra-ui/checkbox'; -import { useCheckbox } from '@chakra-ui/checkbox'; -import { useColorMode, useColorModeValue, Icon } from '@chakra-ui/react'; -import type { - SystemStyleObject, - ThemingProps, - HTMLChakraProps, -} from '@chakra-ui/system'; -import { - chakra, - forwardRef, - omitThemingProps, -} from '@chakra-ui/system'; -import { dataAttr, __DEV__ } from '@chakra-ui/utils'; -import * as React from 'react'; - -import moonIcon from 'icons/moon.svg'; -import sunIcon from 'icons/sun.svg'; -import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; - -export interface ColorModeTogglerProps - extends Omit, - Omit, keyof UseCheckboxProps>, - ThemingProps<'Switch'> { - trackBg?: string; -} - -const ColorModeToggler = forwardRef((props, ref) => { - const ownProps = omitThemingProps(props); - const { toggleColorMode, colorMode } = useColorMode(); - - const { - state, - getInputProps, - getCheckboxProps, - getRootProps, - } = useCheckbox({ ...ownProps, isChecked: colorMode === 'light' }); - - const trackBg = useColorModeValue('blackAlpha.100', 'whiteAlpha.100'); - const thumbBg = 'white'; - const transitionProps = getDefaultTransitionProps(); - - const trackStyles: SystemStyleObject = React.useMemo(() => ({ - bgColor: props.trackBg || trackBg, - width: '72px', - height: '32px', - borderRadius: 'full', - display: 'inline-flex', - flexShrink: 0, - justifyContent: 'space-between', - boxSizing: 'content-box', - cursor: 'pointer', - ...transitionProps, - transitionDuration: 'ultra-slow', - }), [ props.trackBg, trackBg, transitionProps ]); - - const thumbStyles: SystemStyleObject = React.useMemo(() => ({ - bg: thumbBg, - boxShadow: 'md', - width: '24px', - height: '24px', - borderRadius: 'md', - position: 'absolute', - transform: state.isChecked ? 'translate(44px, 4px)' : 'translate(4px, 4px)', - ...transitionProps, - transitionProperty: 'background-color, transform', - transitionDuration: 'ultra-slow', - }), [ thumbBg, transitionProps, state.isChecked ]); - - return ( - - - - - - - - - ); -}); - -if (__DEV__) { - ColorModeToggler.displayName = 'ColorModeToggler'; -} - -export default ColorModeToggler; diff --git a/ui/snippets/header/Header.pw.tsx b/ui/snippets/header/Header.pw.tsx deleted file mode 100644 index fc8130ce69..0000000000 --- a/ui/snippets/header/Header.pw.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { test, expect } from '@playwright/experimental-ct-react'; -import React from 'react'; - -import TestApp from 'playwright/TestApp'; - -import Header from './Header'; - -test('no auth +@mobile', async({ mount, page }) => { - await mount( - -
- , - ); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 150 } }); -}); - -test.describe('dark mode', () => { - test.use({ colorScheme: 'dark' }); - - test('+@mobile', async({ mount, page }) => { - await mount( - -
- , - ); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 150 } }); - }); -}); diff --git a/ui/snippets/header/Header.tsx b/ui/snippets/header/Header.tsx deleted file mode 100644 index 4a69a6627a..0000000000 --- a/ui/snippets/header/Header.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { HStack, Box, Flex, useColorModeValue } from '@chakra-ui/react'; -import React from 'react'; - -import config from 'configs/app'; -import { useScrollDirection } from 'lib/contexts/scrollDirection'; -import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; -import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; -import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile'; -import SearchBar from 'ui/snippets/searchBar/SearchBar'; - -import Burger from './Burger'; - -type Props = { - isHomePage?: boolean; - renderSearchBar?: () => React.ReactNode; -} - -const Header = ({ isHomePage, renderSearchBar }: Props) => { - const bgColor = useColorModeValue('white', 'black'); - const scrollDirection = useScrollDirection(); - - const searchBar = renderSearchBar ? renderSearchBar() : ; - - return ( - <> - - - - - { config.features.account.isEnabled ? : } - - { !isHomePage && searchBar } - - - { !isHomePage && ( - - - { searchBar } - - { config.features.account.isEnabled && } - - ) } - - - ); -}; - -export default Header; diff --git a/ui/snippets/header/HeaderDesktop.pw.tsx b/ui/snippets/header/HeaderDesktop.pw.tsx new file mode 100644 index 0000000000..cb56f9e4ee --- /dev/null +++ b/ui/snippets/header/HeaderDesktop.pw.tsx @@ -0,0 +1,16 @@ +import { test, expect } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import TestApp from 'playwright/TestApp'; + +import HeaderDesktop from './HeaderDesktop'; + +test('default view +@dark-mode', async({ mount }) => { + const component = await mount( + + + , + ); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/snippets/header/HeaderDesktop.tsx b/ui/snippets/header/HeaderDesktop.tsx new file mode 100644 index 0000000000..5da9566f03 --- /dev/null +++ b/ui/snippets/header/HeaderDesktop.tsx @@ -0,0 +1,47 @@ +import { HStack, Box } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; +import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; +import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop'; +import SearchBar from 'ui/snippets/searchBar/SearchBar'; +import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop'; + +import Burger from './Burger'; + +type Props = { + renderSearchBar?: () => React.ReactNode; + isMarketplaceAppPage?: boolean; +} + +const HeaderDesktop = ({ renderSearchBar, isMarketplaceAppPage }: Props) => { + + const searchBar = renderSearchBar ? renderSearchBar() : ; + + return ( + + { isMarketplaceAppPage && ( + + + + + ) } + + { searchBar } + + + { config.features.account.isEnabled && } + { config.features.blockchainInteraction.isEnabled && } + + + ); +}; + +export default React.memo(HeaderDesktop); diff --git a/ui/snippets/header/HeaderMobile.pw.tsx b/ui/snippets/header/HeaderMobile.pw.tsx new file mode 100644 index 0000000000..90c10640dc --- /dev/null +++ b/ui/snippets/header/HeaderMobile.pw.tsx @@ -0,0 +1,18 @@ +import { test, expect, devices } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import TestApp from 'playwright/TestApp'; + +import HeaderMobile from './HeaderMobile'; + +test.use({ viewport: devices['iPhone 13 Pro'].viewport }); + +test('default view +@dark-mode', async({ mount, page }) => { + await mount( + + + , + ); + + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 150 } }); +}); diff --git a/ui/snippets/header/HeaderMobile.tsx b/ui/snippets/header/HeaderMobile.tsx new file mode 100644 index 0000000000..9901d8b9f9 --- /dev/null +++ b/ui/snippets/header/HeaderMobile.tsx @@ -0,0 +1,61 @@ +import { Box, Flex, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; +import { useInView } from 'react-intersection-observer'; + +import config from 'configs/app'; +import { useScrollDirection } from 'lib/contexts/scrollDirection'; +import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; +import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile'; +import SearchBar from 'ui/snippets/searchBar/SearchBar'; +import WalletMenuMobile from 'ui/snippets/walletMenu/WalletMenuMobile'; + +import Burger from './Burger'; + +type Props = { + isHomePage?: boolean; + renderSearchBar?: () => React.ReactNode; +} + +const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => { + const bgColor = useColorModeValue('white', 'black'); + const scrollDirection = useScrollDirection(); + const { ref, inView } = useInView({ threshold: 1 }); + + const searchBar = renderSearchBar ? renderSearchBar() : ; + + return ( + + + + + + { config.features.account.isEnabled ? : } + { config.features.blockchainInteraction.isEnabled && } + + + { !isHomePage && searchBar } + + ); +}; + +export default React.memo(HeaderMobile); diff --git a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png index d2216d9218..f31e4faf10 100644 Binary files a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png and b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_auth-base-view-1.png differ diff --git a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png index 23d1b90106..e180358c91 100644 Binary files a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png and b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_base-view-1.png differ diff --git a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png index 82714d0ab5..ed951f37b4 100644 Binary files a/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png and b/ui/snippets/header/__screenshots__/Burger.pw.tsx_default_dark-mode-base-view-1.png differ diff --git a/ui/snippets/header/__screenshots__/Header.pw.tsx_default_dark-mode-mobile-1.png b/ui/snippets/header/__screenshots__/Header.pw.tsx_default_dark-mode-mobile-1.png index 63bb065c8b..30293cf379 100644 Binary files a/ui/snippets/header/__screenshots__/Header.pw.tsx_default_dark-mode-mobile-1.png and b/ui/snippets/header/__screenshots__/Header.pw.tsx_default_dark-mode-mobile-1.png differ diff --git a/ui/snippets/header/__screenshots__/Header.pw.tsx_default_no-auth-mobile-1.png b/ui/snippets/header/__screenshots__/Header.pw.tsx_default_no-auth-mobile-1.png index 34ba5d84f0..2530860b2c 100644 Binary files a/ui/snippets/header/__screenshots__/Header.pw.tsx_default_no-auth-mobile-1.png and b/ui/snippets/header/__screenshots__/Header.pw.tsx_default_no-auth-mobile-1.png differ diff --git a/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_dark-mode-mobile-1.png b/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_dark-mode-mobile-1.png index 57620369ca..f4a2a016ad 100644 Binary files a/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_dark-mode-mobile-1.png and b/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_dark-mode-mobile-1.png differ diff --git a/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_no-auth-mobile-1.png b/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_no-auth-mobile-1.png index d40f3d88dd..d771a74802 100644 Binary files a/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_no-auth-mobile-1.png and b/ui/snippets/header/__screenshots__/Header.pw.tsx_mobile_no-auth-mobile-1.png differ diff --git a/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_dark-color-mode_default-view-dark-mode-1.png b/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_dark-color-mode_default-view-dark-mode-1.png new file mode 100644 index 0000000000..20aa839385 Binary files /dev/null and b/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_dark-color-mode_default-view-dark-mode-1.png differ diff --git a/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_default_default-view-dark-mode-1.png b/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_default_default-view-dark-mode-1.png new file mode 100644 index 0000000000..d143733147 Binary files /dev/null and b/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_default_default-view-dark-mode-1.png differ diff --git a/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_dark-color-mode_default-view-dark-mode-1.png b/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_dark-color-mode_default-view-dark-mode-1.png new file mode 100644 index 0000000000..01dba03498 Binary files /dev/null and b/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_dark-color-mode_default-view-dark-mode-1.png differ diff --git a/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_default_default-view-dark-mode-1.png b/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_default_default-view-dark-mode-1.png new file mode 100644 index 0000000000..803a3f79ec Binary files /dev/null and b/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_default_default-view-dark-mode-1.png differ diff --git a/ui/snippets/navigation/NavLink.tsx b/ui/snippets/navigation/NavLink.tsx index 9a789a9fae..67dbbe1f6f 100644 --- a/ui/snippets/navigation/NavLink.tsx +++ b/ui/snippets/navigation/NavLink.tsx @@ -1,4 +1,4 @@ -import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp, Icon } from '@chakra-ui/react'; +import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp } from '@chakra-ui/react'; import NextLink from 'next/link'; import React from 'react'; @@ -6,9 +6,9 @@ import type { NavItem } from 'types/client/navigation-items'; import { route } from 'nextjs-routes'; -import arrowIcon from 'icons/arrows/north-east.svg'; import useIsMobile from 'lib/hooks/useIsMobile'; import { isInternalItem } from 'lib/hooks/useNavItems'; +import IconSvg from 'ui/shared/IconSvg'; import NavLinkIcon from './NavLinkIcon'; import useColors from './useColors'; @@ -63,7 +63,7 @@ const NavLink = ({ item, isCollapsed, px, className, onClick }: Props) => { { item.text } - { !isInternalLink && } + { !isInternalLink && } diff --git a/ui/snippets/navigation/NavLinkGroupDesktop.tsx b/ui/snippets/navigation/NavLinkGroupDesktop.tsx index f5b6ee88a2..00677576d9 100644 --- a/ui/snippets/navigation/NavLinkGroupDesktop.tsx +++ b/ui/snippets/navigation/NavLinkGroupDesktop.tsx @@ -1,5 +1,4 @@ import { - Icon, Text, HStack, Box, @@ -14,7 +13,7 @@ import React from 'react'; import type { NavGroupItem } from 'types/client/navigation-items'; -import chevronIcon from 'icons/arrows/east-mini.svg'; +import IconSvg from 'ui/shared/IconSvg'; import NavLink from './NavLink'; import NavLinkIcon from './NavLinkIcon'; @@ -53,8 +52,8 @@ const NavLinkGroupDesktop = ({ item, isCollapsed }: Props) => { > { item.text } - void; + isExpanded?: boolean; } -const NavLinkGroup = ({ item, onClick }: Props) => { - const styleProps = useNavLinkStyleProps({ isActive: item.isActive }); +const NavLinkGroup = ({ item, onClick, isExpanded }: Props) => { + const styleProps = useNavLinkStyleProps({ isActive: item.isActive, isExpanded }); return ( @@ -39,7 +39,7 @@ const NavLinkGroup = ({ item, onClick }: Props) => { { item.text } - + diff --git a/ui/snippets/navigation/NavLinkIcon.tsx b/ui/snippets/navigation/NavLinkIcon.tsx index fb1816ca67..6509d92584 100644 --- a/ui/snippets/navigation/NavLinkIcon.tsx +++ b/ui/snippets/navigation/NavLinkIcon.tsx @@ -1,11 +1,12 @@ -import { Icon } from '@chakra-ui/react'; import React from 'react'; import type { NavItem, NavGroupItem } from 'types/client/navigation-items'; +import IconSvg from 'ui/shared/IconSvg'; + const NavLinkIcon = ({ item }: { item: NavItem | NavGroupItem}) => { - if ('icon' in item) { - return ; + if ('icon' in item && item.icon) { + return ; } if ('iconComponent' in item && item.iconComponent) { const IconComponent = item.iconComponent; diff --git a/ui/snippets/navigation/NavigationDesktop.pw.tsx b/ui/snippets/navigation/NavigationDesktop.pw.tsx index e84f351796..b47d6b59cb 100644 --- a/ui/snippets/navigation/NavigationDesktop.pw.tsx +++ b/ui/snippets/navigation/NavigationDesktop.pw.tsx @@ -110,7 +110,8 @@ test.describe('with tooltips', () => { { hooksConfig }, ); - await page.locator('svg[aria-label="Expand/Collapse menu"]').click(); + await component.locator('header').hover(); + await page.locator('div[aria-label="Expand/Collapse menu"]').click(); await page.locator('a[aria-label="Tokens link"]').hover(); await expect(component).toHaveScreenshot(); @@ -212,3 +213,37 @@ base.describe('cookie set to true', () => { expect(await networkMenu.isVisible()).toBe(false); }); }); + +test('hover +@dark-mode', async({ mount }) => { + const component = await mount( + + + + + + , + { hooksConfig }, + ); + + await component.locator('header').hover(); + await expect(component).toHaveScreenshot(); +}); + +test.describe('hover xl screen', () => { + test.use({ viewport: configs.viewport.xl }); + + test('+@dark-mode', async({ mount }) => { + const component = await mount( + + + + + + , + { hooksConfig }, + ); + + await component.locator('header').hover(); + await expect(component).toHaveScreenshot(); + }); +}); diff --git a/ui/snippets/navigation/NavigationDesktop.tsx b/ui/snippets/navigation/NavigationDesktop.tsx index bb1a3c35aa..7f04ce8c87 100644 --- a/ui/snippets/navigation/NavigationDesktop.tsx +++ b/ui/snippets/navigation/NavigationDesktop.tsx @@ -1,14 +1,13 @@ -import { Flex, Box, VStack, Icon, useColorModeValue } from '@chakra-ui/react'; +import { Flex, Box, VStack, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; import config from 'configs/app'; -import chevronIcon from 'icons/arrows/east-mini.svg'; -import testnetIcon from 'icons/testnet.svg'; import { useAppContext } from 'lib/contexts/app'; import * as cookies from 'lib/cookies'; import useHasAccount from 'lib/hooks/useHasAccount'; import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; +import IconSvg from 'ui/shared/IconSvg'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkMenu from 'ui/snippets/networkMenu/NetworkMenu'; @@ -59,8 +58,13 @@ const NavigationDesktop = () => { py={ 12 } width={{ lg: isExpanded ? '229px' : '92px', xl: isCollapsed ? '92px' : '229px' }} { ...getDefaultTransitionProps({ transitionProperty: 'width, padding' }) } + sx={{ + '&:hover #expand-icon': { + display: 'block', + }, + }} > - { config.chain.isTestnet && } + { config.chain.isTestnet && } { ) } - { cursor="pointer" onClick={ handleTogglerClick } aria-label="Expand/Collapse menu" + id="expand-icon" + display="none" /> ); diff --git a/ui/snippets/navigation/NavigationMobile.tsx b/ui/snippets/navigation/NavigationMobile.tsx index a55365793d..bfa06737b2 100644 --- a/ui/snippets/navigation/NavigationMobile.tsx +++ b/ui/snippets/navigation/NavigationMobile.tsx @@ -1,19 +1,20 @@ -import { Box, Flex, Text, Icon, VStack, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex, Text, VStack, useColorModeValue } from '@chakra-ui/react'; import { animate, motion, useMotionValue } from 'framer-motion'; import React, { useCallback } from 'react'; -import chevronIcon from 'icons/arrows/east-mini.svg'; import useHasAccount from 'lib/hooks/useHasAccount'; import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems'; +import IconSvg from 'ui/shared/IconSvg'; import NavLink from 'ui/snippets/navigation/NavLink'; import NavLinkGroupMobile from './NavLinkGroupMobile'; interface Props { onNavLinkClick?: () => void; + isMarketplaceAppPage?: boolean; } -const NavigationMobile = ({ onNavLinkClick }: Props) => { +const NavigationMobile = ({ onNavLinkClick, isMarketplaceAppPage }: Props) => { const { mainNavItems, accountNavItems } = useNavItems(); const [ openedGroupIndex, setOpenedGroupIndex ] = React.useState(-1); @@ -38,6 +39,8 @@ const NavigationMobile = ({ onNavLinkClick }: Props) => { const openedItem = mainNavItems[openedGroupIndex]; + const isCollapsed = isMarketplaceAppPage ? false : undefined; + return ( { > { mainNavItems.map((item, index) => { if (isGroupItem(item)) { - return ; + return ; } else { - return ; + return ; } }) } @@ -77,7 +80,7 @@ const NavigationMobile = ({ onNavLinkClick }: Props) => { borderColor="divider" > - { accountNavItems.map((item) => ) } + { accountNavItems.map((item) => ) } ) } @@ -93,7 +96,7 @@ const NavigationMobile = ({ onNavLinkClick }: Props) => { key="sub" > - + { mainNavItems[openedGroupIndex].text } { borderColor: 'divider', }} > - { item.map(subItem => ) } + { item.map(subItem => ) } ) : - , + , ) } diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png index 4bd1cd0f03..97fe896a5f 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-xl-screen-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-xl-screen-dark-mode-1.png index 2ee49bf63a..383bfd6258 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_auth-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-dark-mode-1.png new file mode 100644 index 0000000000..f2f27a55fa Binary files /dev/null and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png new file mode 100644 index 0000000000..e20824bea5 Binary files /dev/null and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_hover-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-dark-mode-1.png index 32cb26b760..e6ad44c5a7 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png index 3dd314a9f9..3695536739 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_dark-color-mode_no-auth-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-dark-mode-1.png index d06a03cda9..5a7fa93bd3 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-xl-screen-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-xl-screen-dark-mode-1.png index a572aecf22..386efd593b 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_auth-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_hover-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_hover-dark-mode-1.png new file mode 100644 index 0000000000..dda438430f Binary files /dev/null and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_hover-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png new file mode 100644 index 0000000000..9c50682159 Binary files /dev/null and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_hover-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-dark-mode-1.png index 469eda3d7e..e3e6a366c5 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png index 6ac8b1c198..e02942f1c8 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_no-auth-xl-screen-dark-mode-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-1.png index 5df12bc68e..19dc92c767 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-1.png index 64f15ff3b5..e5a02b29ce 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-submenu-xl-screen-1.png differ diff --git a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-tooltips-1.png b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-tooltips-1.png index 8bd548bfc0..06673a6c6d 100644 Binary files a/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-tooltips-1.png and b/ui/snippets/navigation/__screenshots__/NavigationDesktop.pw.tsx_default_with-tooltips-1.png differ diff --git a/ui/snippets/networkMenu/NetworkLogo.tsx b/ui/snippets/networkMenu/NetworkLogo.tsx index 99a18ca906..d2157651d4 100644 --- a/ui/snippets/networkMenu/NetworkLogo.tsx +++ b/ui/snippets/networkMenu/NetworkLogo.tsx @@ -1,11 +1,10 @@ -import { Icon, Box, Image, useColorModeValue, Skeleton } from '@chakra-ui/react'; +import { Box, Image, useColorModeValue, Skeleton } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import iconPlaceholder from 'icons/networks/icon-placeholder.svg'; -import logoPlaceholder from 'icons/networks/logo-placeholder.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { isCollapsed?: boolean; @@ -31,8 +30,8 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall } return ( - { const iconStyle = useColorModeValue({}, !config.UI.sidebar.icon.dark ? darkModeFilter : {}); return ( - // TODO switch to when main page for network will be ready - ) : ( - @@ -54,8 +53,8 @@ const NetworkMenuLink = ({ title, icon, isActive, isMobile, url, invertIconInDar { title } { isActive && ( - diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx index 1fa67bf456..858c26f729 100644 --- a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx +++ b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx @@ -23,7 +23,7 @@ test('no auth', async({ mount, page }) => { { hooksConfig }, ); - await component.locator('.identicon').click(); + await component.locator('a').click(); expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`); }); diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.tsx index 8d5dea0a70..90e445ec47 100644 --- a/ui/snippets/profileMenu/ProfileMenuDesktop.tsx +++ b/ui/snippets/profileMenu/ProfileMenuDesktop.tsx @@ -1,5 +1,5 @@ -import type { ButtonProps } from '@chakra-ui/react'; -import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button } from '@chakra-ui/react'; +import type { IconButtonProps } from '@chakra-ui/react'; +import { Popover, PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box } from '@chakra-ui/react'; import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; @@ -8,9 +8,16 @@ import * as mixpanel from 'lib/mixpanel/index'; import UserAvatar from 'ui/shared/UserAvatar'; import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; -const ProfileMenuDesktop = () => { +import useMenuButtonColors from '../useMenuButtonColors'; + +type Props = { + isHomePage?: boolean; +}; + +const ProfileMenuDesktop = ({ isHomePage }: Props) => { const { data, error, isPending } = useFetchProfileInfo(); const loginUrl = useLoginUrl(); + const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors(); const [ hasMenu, setHasMenu ] = React.useState(false); React.useEffect(() => { @@ -27,7 +34,7 @@ const ProfileMenuDesktop = () => { ); }, []); - const buttonProps: Partial = (() => { + const iconButtonProps: Partial = (() => { if (hasMenu || !loginUrl) { return {}; } @@ -39,19 +46,53 @@ const ProfileMenuDesktop = () => { }; })(); + const variant = React.useMemo(() => { + if (hasMenu) { + return 'subtle'; + } + return isHomePage ? 'solid' : 'outline'; + }, [ hasMenu, isHomePage ]); + + let iconButtonStyles: Partial = {}; + if (hasMenu) { + iconButtonStyles = { + bg: isHomePage ? 'blue.50' : themedBackground, + }; + } else if (isHomePage) { + iconButtonStyles = { + color: 'white', + }; + } else { + iconButtonStyles = { + borderColor: themedBorderColor, + color: themedColor, + }; + } + return ( - - - + Sign in to My Account to add tags,
create watchlists, access API keys and more } + textAlign="center" + padding={ 2 } + isDisabled={ hasMenu } + openDelay={ 500 } + > + + + } + variant={ variant } + colorScheme="blue" + boxSize="40px" + flexShrink={ 0 } + { ...iconButtonProps } + { ...iconButtonStyles } + /> + + +
{ hasMenu && ( diff --git a/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx b/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx index 627c998353..b5d3fcc373 100644 --- a/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx +++ b/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx @@ -23,7 +23,7 @@ test('no auth', async({ mount, page }) => { { hooksConfig }, ); - await component.locator('.identicon').click(); + await component.locator('a').click(); expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`); }); diff --git a/ui/snippets/profileMenu/ProfileMenuMobile.tsx b/ui/snippets/profileMenu/ProfileMenuMobile.tsx index bce294a20c..0a1b97eda6 100644 --- a/ui/snippets/profileMenu/ProfileMenuMobile.tsx +++ b/ui/snippets/profileMenu/ProfileMenuMobile.tsx @@ -1,5 +1,5 @@ -import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, Button } from '@chakra-ui/react'; -import type { ButtonProps } from '@chakra-ui/react'; +import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react'; +import type { IconButtonProps } from '@chakra-ui/react'; import React from 'react'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; @@ -8,11 +8,13 @@ import * as mixpanel from 'lib/mixpanel/index'; import UserAvatar from 'ui/shared/UserAvatar'; import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent'; +import useMenuButtonColors from '../useMenuButtonColors'; + const ProfileMenuMobile = () => { const { isOpen, onOpen, onClose } = useDisclosure(); - const { data, error, isPending } = useFetchProfileInfo(); const loginUrl = useLoginUrl(); + const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors(); const [ hasMenu, setHasMenu ] = React.useState(false); const handleSignInClick = React.useCallback(() => { @@ -29,7 +31,7 @@ const ProfileMenuMobile = () => { } }, [ data, error?.status, isPending ]); - const buttonProps: Partial = (() => { + const iconButtonProps: Partial = (() => { if (hasMenu || !loginUrl) { return {}; } @@ -43,17 +45,19 @@ const ProfileMenuMobile = () => { return ( <> - - - + } + variant={ data?.avatar ? 'subtle' : 'outline' } + colorScheme="gray" + boxSize="40px" + flexShrink={ 0 } + bg={ data?.avatar ? themedBackground : undefined } + color={ themedColor } + borderColor={ !data?.avatar ? themedBorderColor : undefined } + onClick={ hasMenu ? onOpen : undefined } + { ...iconButtonProps } + /> { hasMenu && ( { await mount( - - - + , ); await page.getByPlaceholder(/search/i).type('o'); diff --git a/ui/snippets/searchBar/SearchBar.tsx b/ui/snippets/searchBar/SearchBar.tsx index 54800d7237..f8b07afe1e 100644 --- a/ui/snippets/searchBar/SearchBar.tsx +++ b/ui/snippets/searchBar/SearchBar.tsx @@ -1,7 +1,7 @@ -import { Box, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, PopoverFooter } from '@chakra-ui/react'; +import { Box, Portal, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, PopoverFooter, useOutsideClick } from '@chakra-ui/react'; import _debounce from 'lodash/debounce'; import { useRouter } from 'next/router'; -import type { FormEvent, FocusEvent } from 'react'; +import type { FormEvent } from 'react'; import React from 'react'; import { Element } from 'react-scroll'; @@ -59,13 +59,15 @@ const SearchBar = ({ isHomepage }: Props) => { inputRef.current?.querySelector('input')?.blur(); }, [ onClose ]); - const handleBlur = React.useCallback((event: FocusEvent) => { - const isFocusInMenu = menuRef.current?.contains(event.relatedTarget); - const isFocusInInput = inputRef.current?.contains(event.relatedTarget); - if (!isFocusInMenu && !isFocusInInput) { - onClose(); + const handleOutsideClick = React.useCallback((event: Event) => { + const isFocusInInput = inputRef.current?.contains(event.target as Node); + + if (!isFocusInInput) { + handelHide(); } - }, [ onClose ]); + }, [ handelHide ]); + + useOutsideClick({ ref: menuRef, handler: handleOutsideClick }); const handleClear = React.useCallback(() => { handleSearchTermChange(''); @@ -118,59 +120,54 @@ const SearchBar = ({ isHomepage }: Props) => { onChange={ handleSearchTermChange } onSubmit={ handleSubmit } onFocus={ handleFocus } - onBlur={ handleBlur } onHide={ handelHide } onClear={ handleClear } isHomepage={ isHomepage } value={ searchTerm } /> - - + - - { searchTerm.trim().length === 0 && recentSearchKeywords.length > 0 && ( - - ) } - { searchTerm.trim().length > 0 && ( - - ) } - - - { searchTerm.trim().length > 0 && query.data && query.data.length >= 50 && ( - - + { searchTerm.trim().length === 0 && recentSearchKeywords.length > 0 && ( + + ) } + { searchTerm.trim().length > 0 && ( + + ) } +
+ + { searchTerm.trim().length > 0 && query.data && query.data.length >= 50 && ( + + View all results - - - ) } - + + + ) } + + ); }; diff --git a/ui/snippets/searchBar/SearchBarInput.tsx b/ui/snippets/searchBar/SearchBarInput.tsx index 179a01a5cf..c8c9d30265 100644 --- a/ui/snippets/searchBar/SearchBarInput.tsx +++ b/ui/snippets/searchBar/SearchBarInput.tsx @@ -1,12 +1,12 @@ -import { InputGroup, Input, InputLeftElement, Icon, chakra, useColorModeValue, forwardRef, InputRightElement } from '@chakra-ui/react'; +import { InputGroup, Input, InputLeftElement, chakra, useColorModeValue, forwardRef, InputRightElement } from '@chakra-ui/react'; import throttle from 'lodash/throttle'; import React from 'react'; import type { ChangeEvent, FormEvent, FocusEvent } from 'react'; -import searchIcon from 'icons/search.svg'; import { useScrollDirection } from 'lib/contexts/scrollDirection'; import useIsMobile from 'lib/hooks/useIsMobile'; import ClearButton from 'ui/shared/ClearButton'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { onChange: (value: string) => void; @@ -20,24 +20,33 @@ interface Props { } const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHide, onClear, value }: Props, ref: React.ForwardedRef) => { + const innerRef = React.useRef(null); + React.useImperativeHandle(ref, () => innerRef.current as HTMLFormElement, []); const [ isSticky, setIsSticky ] = React.useState(false); const scrollDirection = useScrollDirection(); const isMobile = useIsMobile(); const handleScroll = React.useCallback(() => { - if (window.pageYOffset !== 0) { - setIsSticky(true); - } else { - setIsSticky(false); + const TOP_BAR_HEIGHT = 36; + if (!isHomepage) { + if (window.scrollY >= TOP_BAR_HEIGHT) { + setIsSticky(true); + } else { + setIsSticky(false); + } } - }, [ ]); + const clientRect = isMobile && innerRef?.current?.getBoundingClientRect(); + if (clientRect && clientRect.y < TOP_BAR_HEIGHT) { + onHide?.(); + } + }, [ isMobile, onHide, isHomepage ]); const handleChange = React.useCallback((event: ChangeEvent) => { onChange(event.target.value); }, [ onChange ]); React.useEffect(() => { - if (!isMobile || isHomepage) { + if (!isMobile) { return; } const throttledHandleScroll = throttle(handleScroll, 300); @@ -47,33 +56,25 @@ const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHid return () => { window.removeEventListener('scroll', throttledHandleScroll); }; - // replicate componentDidMount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ isMobile ]); + }, [ isMobile, handleScroll ]); const bgColor = useColorModeValue('white', 'black'); const transformMobile = scrollDirection !== 'down' ? 'translateY(0)' : 'translateY(-100%)'; - React.useEffect(() => { - if (isMobile && scrollDirection === 'down') { - onHide?.(); - } - }, [ scrollDirection, onHide, isMobile ]); - return ( - + > - { data.external && } + { data.external && }
> { data.description } - { data.external && } + { data.external && } ); })(); diff --git a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestLabel.tsx b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestLabel.tsx index 9821781595..1e1ffcadb9 100644 --- a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestLabel.tsx +++ b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestLabel.tsx @@ -1,12 +1,11 @@ -import { Grid, Text, Flex, Icon } from '@chakra-ui/react'; +import { Grid, Text, Flex } from '@chakra-ui/react'; import React from 'react'; import type { SearchResultLabel } from 'types/api/search'; -import labelIcon from 'icons/publictags_slim.svg'; -import iconSuccess from 'icons/status/success.svg'; import highlightText from 'lib/highlightText'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { data: SearchResultLabel; @@ -15,7 +14,7 @@ interface Props { } const SearchBarSuggestLabel = ({ data, isMobile, searchTerm }: Props) => { - const icon = ; + const icon = ; const name = ( { ); - const isContractVerified = data.is_smart_contract_verified && ; + const isContractVerified = data.is_smart_contract_verified && ; if (isMobile) { return ( diff --git a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestToken.tsx b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestToken.tsx index 951bc9923c..7635e40ddd 100644 --- a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestToken.tsx +++ b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestToken.tsx @@ -1,13 +1,12 @@ -import { Grid, Text, Flex, Icon } from '@chakra-ui/react'; +import { Grid, Text, Flex } from '@chakra-ui/react'; import React from 'react'; import type { SearchResultToken } from 'types/api/search'; -import iconSuccess from 'icons/status/success.svg'; -import verifiedToken from 'icons/verified_token.svg'; import highlightText from 'lib/highlightText'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { data: SearchResultToken; @@ -17,7 +16,7 @@ interface Props { const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => { const icon = ; - const verifiedIcon = ; + const verifiedIcon = ; const name = ( { ); - const contractVerifiedIcon = data.is_smart_contract_verified && ; + const contractVerifiedIcon = data.is_smart_contract_verified && ; const additionalInfo = ( { data.token_type === 'ERC-20' && data.exchange_rate && `$${ Number(data.exchange_rate).toLocaleString() }` } diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_dark-color-mode_search-by-name-homepage-dark-mode-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_dark-color-mode_search-by-name-homepage-dark-mode-1.png index e0242a33c5..a9ee87486e 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_dark-color-mode_search-by-name-homepage-dark-mode-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_dark-color-mode_search-by-name-homepage-dark-mode-1.png differ diff --git a/ui/snippets/topBar/ColorModeSwitch.tsx b/ui/snippets/topBar/ColorModeSwitch.tsx new file mode 100644 index 0000000000..aa7156a1e1 --- /dev/null +++ b/ui/snippets/topBar/ColorModeSwitch.tsx @@ -0,0 +1,98 @@ +import { + IconButton, + Popover, + PopoverTrigger, + PopoverContent, + PopoverBody, + useColorMode, + useDisclosure, + Skeleton, +} from '@chakra-ui/react'; +import React from 'react'; + +import * as cookies from 'lib/cookies'; +import IconSvg from 'ui/shared/IconSvg'; + +import ColorModeSwitchTheme from './ColorModeSwitchTheme'; +import { COLOR_THEMES } from './utils'; + +const ColorModeSwitch = () => { + const { isOpen, onToggle, onClose } = useDisclosure(); + const { setColorMode, colorMode } = useColorMode(); + + const [ activeHex, setActiveHex ] = React.useState(); + + const setTheme = React.useCallback((hex: string) => { + const nextTheme = COLOR_THEMES.find((theme) => theme.colors.some((color) => color.hex === hex)); + + if (!nextTheme) { + return; + } + + setColorMode(nextTheme.colorMode); + + const varName = nextTheme.colorMode === 'light' ? '--chakra-colors-white' : '--chakra-colors-black'; + window.document.documentElement.style.setProperty(varName, hex); + + cookies.set(cookies.NAMES.COLOR_MODE_HEX, hex); + window.localStorage.setItem(cookies.NAMES.COLOR_MODE, nextTheme.colorMode); + }, [ setColorMode ]); + + React.useEffect(() => { + const cookieColorMode = cookies.get(cookies.NAMES.COLOR_MODE); + + const nextColorMode = (() => { + if (!cookieColorMode) { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + return colorMode; + })(); + + const fallbackHex = (COLOR_THEMES.find(theme => theme.colorMode === nextColorMode && theme.colors.length === 1) ?? COLOR_THEMES[0]).colors[0].hex; + const cookieHex = cookies.get(cookies.NAMES.COLOR_MODE_HEX) ?? fallbackHex; + setTheme(cookieHex); + setActiveHex(cookieHex); + // should run only on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ ]); + + const handleSelect = React.useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + + const hex = event.currentTarget.getAttribute('data-hex'); + + if (!hex) { + return; + } + + setTheme(hex); + setActiveHex(hex); + }, [ setTheme ]); + + const activeTheme = COLOR_THEMES.find((theme) => theme.colors.some((color) => color.hex === activeHex)); + + return ( + + + { activeTheme ? ( + } + boxSize={ 5 } + onClick={ onToggle } + /> + ) : } + + + + { COLOR_THEMES.map((theme) => ) } + + + + ); +}; + +export default ColorModeSwitch; diff --git a/ui/snippets/topBar/ColorModeSwitchSample.tsx b/ui/snippets/topBar/ColorModeSwitchSample.tsx new file mode 100644 index 0000000000..a1a8a5b4e4 --- /dev/null +++ b/ui/snippets/topBar/ColorModeSwitchSample.tsx @@ -0,0 +1,54 @@ +import { + Box, + useColorModeValue, + useToken, +} from '@chakra-ui/react'; +import React from 'react'; + +import type { ColorThemeColor } from './utils'; + +interface Props extends ColorThemeColor { + onClick?: (event: React.MouseEvent) => void; + isActive: boolean; +} + +const ColorModeSwitchSample = ({ hex, sampleBg, onClick, isActive }: Props) => { + const bgColor = useColorModeValue('white', 'gray.900'); + const activeBgColor = useColorModeValue('blue.50', 'blackAlpha.800'); + + const activeBorderColor = useToken('colors', useColorModeValue('blackAlpha.800', 'gray.50')); + const hoverBorderColor = useToken('colors', 'link_hovered'); + + return ( + + ); +}; + +export default ColorModeSwitchSample; diff --git a/ui/snippets/topBar/ColorModeSwitchTheme.tsx b/ui/snippets/topBar/ColorModeSwitchTheme.tsx new file mode 100644 index 0000000000..d69608ed79 --- /dev/null +++ b/ui/snippets/topBar/ColorModeSwitchTheme.tsx @@ -0,0 +1,56 @@ +import { + Flex, + useColorModeValue, + useToken, +} from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import ColorModeSwitchSample from './ColorModeSwitchSample'; +import type { ColorTheme } from './utils'; + +interface Props extends ColorTheme { + onClick?: (event: React.MouseEvent) => void; + activeHex: string | undefined; +} + +const ColorModeSwitchTheme = ({ icon, name, colors, onClick, activeHex }: Props) => { + const isActive = colors.some((sample) => sample.hex === activeHex); + const activeColor = useColorModeValue('blackAlpha.800', 'gray.50'); + const activeBgColor = useColorModeValue('blue.50', 'blackAlpha.800'); + const inactiveColor = useColorModeValue('blue.700', 'gray.400'); + const hoverBorderColor = useToken('colors', 'link_hovered'); + const hasOneColor = colors.length === 1; + + return ( + + + { name } + + { colors.map((sample) => ) } + + + ); +}; + +export default ColorModeSwitchTheme; diff --git a/ui/snippets/topBar/TopBar.pw.tsx b/ui/snippets/topBar/TopBar.pw.tsx new file mode 100644 index 0000000000..555f645707 --- /dev/null +++ b/ui/snippets/topBar/TopBar.pw.tsx @@ -0,0 +1,27 @@ +import { test, expect } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import * as statsMock from 'mocks/stats/index'; +import TestApp from 'playwright/TestApp'; +import buildApiUrl from 'playwright/utils/buildApiUrl'; + +import TopBar from './TopBar'; + +test('default view +@dark-mode +@mobile', async({ mount, page }) => { + await page.route(buildApiUrl('homepage_stats'), (route) => route.fulfill({ + status: 200, + body: JSON.stringify(statsMock.base), + })); + + const component = await mount( + + + , + ); + + await component.getByText(/gwei/i).hover(); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 220 } }); + + await component.getByLabel('color mode switch').click(); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 220 } }); +}); diff --git a/ui/snippets/topBar/TopBar.tsx b/ui/snippets/topBar/TopBar.tsx new file mode 100644 index 0000000000..23976769a5 --- /dev/null +++ b/ui/snippets/topBar/TopBar.tsx @@ -0,0 +1,23 @@ +import { Flex, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import ColorModeSwitch from './ColorModeSwitch'; +import TopBarStats from './TopBarStats'; + +const TopBar = () => { + const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100'); + + return ( + + + + + ); +}; + +export default React.memo(TopBar); diff --git a/ui/snippets/topBar/TopBarStats.tsx b/ui/snippets/topBar/TopBarStats.tsx new file mode 100644 index 0000000000..758bcfb1a6 --- /dev/null +++ b/ui/snippets/topBar/TopBarStats.tsx @@ -0,0 +1,72 @@ +import { Flex, LightMode, Link, Skeleton, Tooltip, chakra, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; +import useApiQuery from 'lib/api/useApiQuery'; +import { HOMEPAGE_STATS } from 'stubs/stats'; +import GasInfoTooltipContent from 'ui/shared/GasInfoTooltipContent/GasInfoTooltipContent'; +import TextSeparator from 'ui/shared/TextSeparator'; + +const TopBarStats = () => { + // have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107 + const { isOpen, onOpen, onToggle, onClose } = useDisclosure(); + + const handleClick = React.useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + onToggle(); + }, [ onToggle ]); + + const { data, isPlaceholderData, isError } = useApiQuery('homepage_stats', { + queryOptions: { + placeholderData: HOMEPAGE_STATS, + refetchOnMount: false, + }, + }); + + if (isError) { + return
; + } + + return ( + + { data?.coin_price && ( + + { config.chain.governanceToken.symbol || config.chain.currency.symbol } + ${ Number(data.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }) } + + ) } + { data?.coin_price && config.UI.homepage.showGasTracker && } + { data?.gas_prices && data.gas_prices.average !== null && config.UI.homepage.showGasTracker && ( + + Gas + + } + hasArrow={ false } + borderRadius="md" + offset={ [ 0, 16 ] } + bgColor="blackAlpha.900" + p={ 0 } + isOpen={ isOpen } + > + + { data.gas_prices.average } Gwei + + + + + ) } + + ); +}; + +export default React.memo(TopBarStats); diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-1.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-1.png new file mode 100644 index 0000000000..6a2b7cbc92 Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png new file mode 100644 index 0000000000..5698b69615 Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-1.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-1.png new file mode 100644 index 0000000000..5ad894f330 Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png new file mode 100644 index 0000000000..87b4a3ff01 Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png new file mode 100644 index 0000000000..5f14a03434 Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png new file mode 100644 index 0000000000..cba6ce62e3 Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/utils.ts b/ui/snippets/topBar/utils.ts new file mode 100644 index 0000000000..0644fcc9b2 --- /dev/null +++ b/ui/snippets/topBar/utils.ts @@ -0,0 +1,33 @@ +import type { IconName } from 'ui/shared/IconSvg'; + +export const COLOR_THEMES = [ + { + name: 'Light', + colorMode: 'light', + icon: 'sun' as IconName, + colors: [ + { hex: '#FFFFFF', sampleBg: 'linear-gradient(154deg, #EFEFEF 50%, rgba(255, 255, 255, 0.00) 330.86%)' }, + ], + }, + { + name: 'Dim', + colorMode: 'dark', + icon: 'moon-with-star' as IconName, + colors: [ + { hex: '#232B37', sampleBg: 'linear-gradient(152deg, #232B37 50%, rgba(255, 255, 255, 0.00) 290.71%)' }, + { hex: '#1B2E48', sampleBg: 'linear-gradient(150deg, #1B2E48 50%, rgba(255, 255, 255, 0.00) 312.75%)' }, + ], + }, + { + name: 'Dark', + colorMode: 'dark', + icon: 'moon' as IconName, + colors: [ + { hex: '#101112', sampleBg: 'linear-gradient(161deg, #000 9.37%, #383838 92.52%)' }, + ], + }, +]; + +export type ColorTheme = typeof COLOR_THEMES[number]; + +export type ColorThemeColor = ColorTheme['colors'][number]; diff --git a/ui/snippets/useMenuButtonColors.tsx b/ui/snippets/useMenuButtonColors.tsx new file mode 100644 index 0000000000..1583e18fb5 --- /dev/null +++ b/ui/snippets/useMenuButtonColors.tsx @@ -0,0 +1,9 @@ +import { useColorModeValue } from '@chakra-ui/react'; + +export default function useMenuColors() { + const themedBackground = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); + const themedBorderColor = useColorModeValue('gray.300', 'gray.700'); + const themedColor = useColorModeValue('blackAlpha.800', 'gray.400'); + + return { themedBackground, themedBorderColor, themedColor }; +} diff --git a/ui/snippets/walletMenu/WalletMenuContent.tsx b/ui/snippets/walletMenu/WalletMenuContent.tsx new file mode 100644 index 0000000000..2939d73f7b --- /dev/null +++ b/ui/snippets/walletMenu/WalletMenuContent.tsx @@ -0,0 +1,46 @@ +import { Box, Button, Text } from '@chakra-ui/react'; +import React from 'react'; + +import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; + +type Props = { + address?: string; + disconnect?: () => void; +}; + +const WalletMenuContent = ({ address, disconnect }: Props) => ( + + + My wallet + + + Your wallet is used to interact with apps and contracts in the explorer. + + + + +); + +export default WalletMenuContent; diff --git a/ui/snippets/walletMenu/WalletMenuDesktop.tsx b/ui/snippets/walletMenu/WalletMenuDesktop.tsx new file mode 100644 index 0000000000..14265b2349 --- /dev/null +++ b/ui/snippets/walletMenu/WalletMenuDesktop.tsx @@ -0,0 +1,96 @@ +import type { ButtonProps } from '@chakra-ui/react'; +import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button, Box, useBoolean } from '@chakra-ui/react'; +import React from 'react'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon'; +import HashStringShorten from 'ui/shared/HashStringShorten'; +import useWallet from 'ui/snippets/walletMenu/useWallet'; +import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent'; + +import useMenuButtonColors from '../useMenuButtonColors'; +import WalletTooltip from './WalletTooltip'; + +type Props = { + isHomePage?: boolean; +}; + +const WalletMenuDesktop = ({ isHomePage }: Props) => { + const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet(); + const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors(); + const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean(false); + const isMobile = useIsMobile(); + + const variant = React.useMemo(() => { + if (isWalletConnected) { + return 'subtle'; + } + return isHomePage ? 'solid' : 'outline'; + }, [ isWalletConnected, isHomePage ]); + + let buttonStyles: Partial = {}; + if (isWalletConnected) { + buttonStyles = { + bg: isHomePage ? 'blue.50' : themedBackground, + color: isHomePage ? 'blackAlpha.800' : themedColor, + _hover: { + color: isHomePage ? 'blackAlpha.800' : themedColor, + }, + }; + } else if (isHomePage) { + buttonStyles = { + color: 'white', + }; + } else { + buttonStyles = { + borderColor: themedBorderColor, + color: themedColor, + }; + } + + return ( + + + + + + + + + { isWalletConnected && ( + + + + + + ) } + + ); +}; + +export default WalletMenuDesktop; diff --git a/ui/snippets/walletMenu/WalletMenuMobile.tsx b/ui/snippets/walletMenu/WalletMenuMobile.tsx new file mode 100644 index 0000000000..9ffd5a3be5 --- /dev/null +++ b/ui/snippets/walletMenu/WalletMenuMobile.tsx @@ -0,0 +1,58 @@ +import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react'; +import React from 'react'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon'; +import IconSvg from 'ui/shared/IconSvg'; +import useWallet from 'ui/snippets/walletMenu/useWallet'; +import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent'; + +import useMenuButtonColors from '../useMenuButtonColors'; +import WalletTooltip from './WalletTooltip'; + +const WalletMenuMobile = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet(); + const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors(); + const isMobile = useIsMobile(); + + return ( + <> + + : + + } + variant={ isWalletConnected ? 'subtle' : 'outline' } + colorScheme="gray" + boxSize="40px" + flexShrink={ 0 } + bg={ isWalletConnected ? themedBackground : undefined } + color={ themedColor } + borderColor={ !isWalletConnected ? themedBorderColor : undefined } + onClick={ isWalletConnected ? onOpen : connect } + isLoading={ isModalOpening || isModalOpen } + /> + + { isWalletConnected && ( + + + + + + + + + ) } + + ); +}; + +export default WalletMenuMobile; diff --git a/ui/snippets/walletMenu/WalletTooltip.tsx b/ui/snippets/walletMenu/WalletTooltip.tsx new file mode 100644 index 0000000000..d0d7b042b3 --- /dev/null +++ b/ui/snippets/walletMenu/WalletTooltip.tsx @@ -0,0 +1,53 @@ +import { Tooltip, useBoolean, useOutsideClick } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; +import React from 'react'; + +type Props = { + children: React.ReactNode; + isDisabled?: boolean; + isMobile?: boolean; +}; + +const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => { + const router = useRouter(); + const [ isTooltipShown, setIsTooltipShown ] = useBoolean(false); + const ref = React.useRef(null); + useOutsideClick({ ref, handler: setIsTooltipShown.off }); + + const { defaultLabel, label, localStorageKey } = React.useMemo(() => { + const isAppPage = router.pathname === '/apps/[id]'; + const defaultLabel = Your wallet is used to interact with
apps and contracts in the explorer
; + const label = isAppPage ? + Connect once to use your wallet with
all apps in the DAppscout marketplace!
: + defaultLabel; + const localStorageKey = `${ isAppPage ? 'dapp-' : '' }wallet-connect-tooltip-shown`; + return { defaultLabel, label, localStorageKey }; + }, [ router.pathname ]); + + React.useEffect(() => { + const wasShown = window.localStorage.getItem(localStorageKey); + if (!isDisabled && !wasShown) { + setIsTooltipShown.on(); + window.localStorage.setItem(localStorageKey, 'true'); + setTimeout(() => setIsTooltipShown.off(), 3000); + } + }, [ setIsTooltipShown, localStorageKey, isDisabled ]); + + return ( + + { children } + + ); +}; + +export default WalletTooltip; diff --git a/ui/snippets/walletMenu/useWallet.tsx b/ui/snippets/walletMenu/useWallet.tsx new file mode 100644 index 0000000000..b1ab280321 --- /dev/null +++ b/ui/snippets/walletMenu/useWallet.tsx @@ -0,0 +1,45 @@ +import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react'; +import React from 'react'; +import { useAccount, useDisconnect } from 'wagmi'; + +import * as mixpanel from 'lib/mixpanel/index'; + +export default function useWallet() { + const { open } = useWeb3Modal(); + const { open: isOpen } = useWeb3ModalState(); + const { disconnect } = useDisconnect(); + const [ isModalOpening, setIsModalOpening ] = React.useState(false); + const [ isClientLoaded, setIsClientLoaded ] = React.useState(false); + + React.useEffect(() => { + setIsClientLoaded(true); + }, []); + + const handleConnect = React.useCallback(async() => { + setIsModalOpening(true); + await open(); + setIsModalOpening(false); + mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: 'Header', Status: 'Started' }); + }, [ open ]); + + const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { + !isReconnected && mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: 'Header', Status: 'Connected' }); + }, []); + + const handleDisconnect = React.useCallback(() => { + disconnect(); + }, [ disconnect ]); + + const { address, isDisconnected } = useAccount({ onConnect: handleAccountConnected }); + + const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined; + + return { + isWalletConnected, + address: address || '', + connect: handleConnect, + disconnect: handleDisconnect, + isModalOpening, + isModalOpen: isOpen, + }; +} diff --git a/ui/stats/NumberWidgetsList.tsx b/ui/stats/NumberWidgetsList.tsx index 53a8b212b7..6014693b41 100644 --- a/ui/stats/NumberWidgetsList.tsx +++ b/ui/stats/NumberWidgetsList.tsx @@ -7,6 +7,8 @@ import { STATS_COUNTER } from 'stubs/stats'; import DataFetchAlert from '../shared/DataFetchAlert'; import NumberWidget from './NumberWidget'; +const UNITS_WITHOUT_SPACE = [ 's' ]; + const NumberWidgetsList = () => { const { data, isPlaceholderData, isError } = useApiQuery('stats_counters', { queryOptions: { @@ -26,11 +28,18 @@ const NumberWidgetsList = () => { { data?.counters?.map(({ id, title, value, units, description }, index) => { + let unitsStr = ''; + if (UNITS_WITHOUT_SPACE.includes(units)) { + unitsStr = units; + } else if (units) { + unitsStr = ' ' + units; + } + return ( diff --git a/ui/stats/StatsDropdownMenu.tsx b/ui/stats/StatsDropdownMenu.tsx index 6f9edbf581..7ab89f9ebe 100644 --- a/ui/stats/StatsDropdownMenu.tsx +++ b/ui/stats/StatsDropdownMenu.tsx @@ -1,7 +1,7 @@ -import { Box, Button, Icon, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, Text } from '@chakra-ui/react'; +import { Box, Button, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, Text } from '@chakra-ui/react'; import React, { useCallback } from 'react'; -import eastMiniArrowIcon from 'icons/arrows/east-mini.svg'; +import IconSvg from 'ui/shared/IconSvg'; type Props = { items: Array<{id: T; title: string}>; @@ -39,7 +39,7 @@ export function StatsDropdownMenu({ items, selectedId, onSelec > { selectedCategory?.title } - + diff --git a/ui/token/TokenInventory.pw.tsx b/ui/token/TokenInventory.pw.tsx index 053ad772b8..d7b75dee28 100644 --- a/ui/token/TokenInventory.pw.tsx +++ b/ui/token/TokenInventory.pw.tsx @@ -2,6 +2,7 @@ import { Box } from '@chakra-ui/react'; import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; +import { tokenInfoERC721a } from 'mocks/tokens/tokenInfo'; import { base as tokenInstanse } from 'mocks/tokens/tokenInstance'; import TestApp from 'playwright/TestApp'; @@ -23,6 +24,11 @@ test('base view +@mobile', async({ mount }) => { // @ts-ignore: pagination: { page: 1, isVisible: true }, }} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: + tokenQuery={{ + data: tokenInfoERC721a, + }} /> , ); diff --git a/ui/token/TokenInventory.tsx b/ui/token/TokenInventory.tsx index 36c17612bf..50153b466c 100644 --- a/ui/token/TokenInventory.tsx +++ b/ui/token/TokenInventory.tsx @@ -1,30 +1,63 @@ -import { Grid } from '@chakra-ui/react'; +import { Flex, Grid, Text } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; +import type { TokenInfo } from 'types/api/token'; + +import type { ResourceError } from 'lib/api/resources'; import useIsMobile from 'lib/hooks/useIsMobile'; import ActionBar from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import Pagination from 'ui/shared/pagination/Pagination'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; +import ResetIconButton from 'ui/shared/ResetIconButton'; import TokenInventoryItem from './TokenInventoryItem'; type Props = { inventoryQuery: QueryWithPagesResult<'token_inventory'>; + tokenQuery: UseQueryResult>; + ownerFilter?: string; } -const TokenInventory = ({ inventoryQuery }: Props) => { +const TokenInventory = ({ inventoryQuery, tokenQuery, ownerFilter }: Props) => { const isMobile = useIsMobile(); - const actionBar = isMobile && inventoryQuery.pagination.isVisible && ( - - - + const resetOwnerFilter = React.useCallback(() => { + inventoryQuery.onFilterChange({}); + }, [ inventoryQuery ]); + + const isActionBarHidden = !ownerFilter && !inventoryQuery.data?.items.length; + + const ownerFilterComponent = ownerFilter && ( + + Filtered by owner + + + + + + ); + + const actionBar = !isActionBarHidden && ( + <> + { ownerFilterComponent } + + { isMobile && } + + ); const items = inventoryQuery.data?.items; + const token = tokenQuery.data; - const content = items ? ( + const content = items && token ? ( { > { items.map((item, index) => ( )) } diff --git a/ui/token/TokenInventoryItem.tsx b/ui/token/TokenInventoryItem.tsx index d816cc3f54..72f650d2b8 100644 --- a/ui/token/TokenInventoryItem.tsx +++ b/ui/token/TokenInventoryItem.tsx @@ -1,7 +1,7 @@ import { Box, Flex, Text, Link, useColorModeValue, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import type { TokenInstance } from 'types/api/token'; +import type { TokenInfo, TokenInstance } from 'types/api/token'; import { route } from 'nextjs-routes'; @@ -11,9 +11,9 @@ import LinkInternal from 'ui/shared/LinkInternal'; import NftMedia from 'ui/shared/nft/NftMedia'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; -type Props = { item: TokenInstance; isLoading: boolean }; +type Props = { item: TokenInstance; token: TokenInfo; isLoading: boolean }; -const NFTItem = ({ item, isLoading }: Props) => { +const TokenInventoryItem = ({ item, token, isLoading }: Props) => { const isMobile = useIsMobile(); @@ -25,7 +25,7 @@ const NFTItem = ({ item, isLoading }: Props) => { /> ); - const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: item.token.address, id: item.id } }); + const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: item.id } }); return ( { ); }; -export default NFTItem; +export default TokenInventoryItem; diff --git a/ui/token/TokenProjectInfo/Content.tsx b/ui/token/TokenProjectInfo/Content.tsx index 0ff58572da..91a7d80704 100644 --- a/ui/token/TokenProjectInfo/Content.tsx +++ b/ui/token/TokenProjectInfo/Content.tsx @@ -3,20 +3,6 @@ import React from 'react'; import type { TokenVerifiedInfo } from 'types/api/token'; -import iconCoinGecko from 'icons/social/coingecko.svg'; -import iconCoinMarketCap from 'icons/social/coinmarketcap.svg'; -import iconDefiLlama from 'icons/social/defi_llama.svg'; -import iconDiscord from 'icons/social/discord_filled.svg'; -import iconFacebook from 'icons/social/facebook_filled.svg'; -import iconGithub from 'icons/social/github_filled.svg'; -import iconLinkedIn from 'icons/social/linkedin_filled.svg'; -import iconMedium from 'icons/social/medium_filled.svg'; -import iconOpenSea from 'icons/social/opensea_filled.svg'; -import iconReddit from 'icons/social/reddit_filled.svg'; -import iconSlack from 'icons/social/slack_filled.svg'; -import iconTelegram from 'icons/social/telegram_filled.svg'; -import iconTwitter from 'icons/social/twitter_filled.svg'; - import DocsLink from './DocsLink'; import type { Props as ServiceLinkProps } from './ServiceLink'; import ServiceLink from './ServiceLink'; @@ -27,22 +13,22 @@ interface Props { } const SOCIAL_LINKS: Array> = [ - { field: 'github', icon: iconGithub, title: 'Github' }, - { field: 'twitter', icon: iconTwitter, title: 'Twitter' }, - { field: 'telegram', icon: iconTelegram, title: 'Telegram' }, - { field: 'openSea', icon: iconOpenSea, title: 'OpenSea' }, - { field: 'linkedin', icon: iconLinkedIn, title: 'LinkedIn' }, - { field: 'facebook', icon: iconFacebook, title: 'Facebook' }, - { field: 'discord', icon: iconDiscord, title: 'Discord' }, - { field: 'medium', icon: iconMedium, title: 'Medium' }, - { field: 'slack', icon: iconSlack, title: 'Slack' }, - { field: 'reddit', icon: iconReddit, title: 'Reddit' }, + { field: 'github', icon: 'social/github_filled', title: 'Github' }, + { field: 'twitter', icon: 'social/twitter_filled', title: 'Twitter' }, + { field: 'telegram', icon: 'social/telegram_filled', title: 'Telegram' }, + { field: 'openSea', icon: 'social/opensea_filled', title: 'OpenSea' }, + { field: 'linkedin', icon: 'social/linkedin_filled', title: 'LinkedIn' }, + { field: 'facebook', icon: 'social/facebook_filled', title: 'Facebook' }, + { field: 'discord', icon: 'social/discord_filled', title: 'Discord' }, + { field: 'medium', icon: 'social/medium_filled', title: 'Medium' }, + { field: 'slack', icon: 'social/slack_filled', title: 'Slack' }, + { field: 'reddit', icon: 'social/reddit_filled', title: 'Reddit' }, ]; const PRICE_TICKERS: Array> = [ - { field: 'coinGeckoTicker', icon: iconCoinGecko, title: 'CoinGecko' }, - { field: 'coinMarketCapTicker', icon: iconCoinMarketCap, title: 'CoinMarketCap' }, - { field: 'defiLlamaTicker', icon: iconDefiLlama, title: 'DefiLlama' }, + { field: 'coinGeckoTicker', icon: 'social/coingecko', title: 'CoinGecko' }, + { field: 'coinMarketCapTicker', icon: 'social/coinmarketcap', title: 'CoinMarketCap' }, + { field: 'defiLlamaTicker', icon: 'social/defi_llama', title: 'DefiLlama' }, ]; export function hasContent(data: TokenVerifiedInfo): boolean { diff --git a/ui/token/TokenProjectInfo/DocsLink.tsx b/ui/token/TokenProjectInfo/DocsLink.tsx index ddb721381a..d6493d672b 100644 --- a/ui/token/TokenProjectInfo/DocsLink.tsx +++ b/ui/token/TokenProjectInfo/DocsLink.tsx @@ -1,7 +1,7 @@ -import { Icon, Link } from '@chakra-ui/react'; +import { Link } from '@chakra-ui/react'; import React from 'react'; -import iconDocs from 'icons/docs.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { href: string; @@ -16,7 +16,7 @@ const DocsLink = ({ href }: Props) => { alignItems="center" columnGap={ 1 } > - + Documentation ); diff --git a/ui/token/TokenProjectInfo/ServiceLink.tsx b/ui/token/TokenProjectInfo/ServiceLink.tsx index aa213ebd3e..5c797cb73f 100644 --- a/ui/token/TokenProjectInfo/ServiceLink.tsx +++ b/ui/token/TokenProjectInfo/ServiceLink.tsx @@ -1,11 +1,14 @@ -import { Link, Icon } from '@chakra-ui/react'; +import { Link } from '@chakra-ui/react'; import React from 'react'; import type { TokenVerifiedInfo } from 'types/api/token'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + export interface Props { field: keyof TokenVerifiedInfo; - icon: React.FunctionComponent>; + icon: IconName; title: string; href?: string; } @@ -20,7 +23,7 @@ const ServiceLink = ({ href, title, icon }: Props) => { display="inline-flex" alignItems="center" > - + { title } ); diff --git a/ui/token/TokenProjectInfo/SupportLink.tsx b/ui/token/TokenProjectInfo/SupportLink.tsx index 3a3d3e2422..083cab35b6 100644 --- a/ui/token/TokenProjectInfo/SupportLink.tsx +++ b/ui/token/TokenProjectInfo/SupportLink.tsx @@ -1,8 +1,7 @@ -import { Icon, Link } from '@chakra-ui/react'; +import { Link } from '@chakra-ui/react'; import React from 'react'; -import iconEmail from 'icons/email.svg'; -import iconLink from 'icons/link.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { url: string; @@ -11,7 +10,6 @@ interface Props { const SupportLink = ({ url }: Props) => { const isEmail = url.includes('@'); const href = isEmail ? `mailto:${ url }` : url; - const icon = isEmail ? iconEmail : iconLink; return ( { alignItems="center" columnGap={ 1 } > - + { url } ); diff --git a/ui/token/TokenProjectInfo/TriggerButton.tsx b/ui/token/TokenProjectInfo/TriggerButton.tsx index e7cbdc7208..fcab97ab21 100644 --- a/ui/token/TokenProjectInfo/TriggerButton.tsx +++ b/ui/token/TokenProjectInfo/TriggerButton.tsx @@ -1,8 +1,7 @@ -import { Button, Icon } from '@chakra-ui/react'; +import { Button } from '@chakra-ui/react'; import React from 'react'; -import arrowIcon from 'icons/arrows/east-mini.svg'; -import rocketIcon from 'icons/rocket.svg'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { onClick: () => void; @@ -22,9 +21,9 @@ const TriggerButton = ({ isOpen, onClick }: Props, ref: React.ForwardedRef - + Info - + ); }; diff --git a/ui/token/TokenTransfer/TokenTransferListItem.tsx b/ui/token/TokenTransfer/TokenTransferListItem.tsx index 2648d06979..95aa336465 100644 --- a/ui/token/TokenTransfer/TokenTransferListItem.tsx +++ b/ui/token/TokenTransfer/TokenTransferListItem.tsx @@ -1,16 +1,15 @@ -import { Flex, Skeleton } from '@chakra-ui/react'; +import { Grid, Flex, Skeleton } from '@chakra-ui/react'; import React from 'react'; import type { TokenTransfer } from 'types/api/tokenTransfer'; -import eastArrowIcon from 'icons/arrows/east.svg'; import getCurrencyValue from 'lib/getCurrencyValue'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntityWithTokenFilter from 'ui/shared/entities/address/AddressEntityWithTokenFilter'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import IconSvg from 'ui/shared/IconSvg'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TruncatedValue from 'ui/shared/TruncatedValue'; @@ -62,7 +61,7 @@ const TokenTransferListItem = ({ width="50%" fontWeight="500" /> - + { valueStr && (token.type === 'ERC-20' || token.type === 'ERC-1155') && ( - + Value - + { valueStr } { token.symbol && } - { usd && (${ usd }) } - + { usd && ( + + (${ usd }) + + ) } + ) } { 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && ( - - - + >; + icon: IconName; label: string; color: string; } const SETTINGS: Record = { - github: { label: 'GitHub', icon: iconGithub, color: 'inherit' }, - telegram: { label: 'Telegram', icon: iconTelegram, color: 'telegram' }, - linkedin: { label: 'LinkedIn', icon: iconLinkedIn, color: 'linkedin' }, - discord: { label: 'Discord', icon: iconDiscord, color: 'discord' }, - slack: { label: 'Slack', icon: iconSlack, color: 'slack' }, - twitter: { label: 'Twitter', icon: iconTwitter, color: 'twitter' }, - opensea: { label: 'OpenSea', icon: iconOpenSea, color: 'opensea' }, - facebook: { label: 'Facebook', icon: iconFacebook, color: 'facebook' }, - medium: { label: 'Medium', icon: iconMedium, color: 'inherit' }, - reddit: { label: 'Reddit', icon: iconReddit, color: 'reddit' }, + github: { label: 'GitHub', icon: 'social/github_filled', color: 'inherit' }, + telegram: { label: 'Telegram', icon: 'social/telegram_filled', color: 'telegram' }, + linkedin: { label: 'LinkedIn', icon: 'social/linkedin_filled', color: 'linkedin' }, + discord: { label: 'Discord', icon: 'social/discord_filled', color: 'discord' }, + slack: { label: 'Slack', icon: 'social/slack_filled', color: 'slack' }, + twitter: { label: 'Twitter', icon: 'social/twitter_filled', color: 'twitter' }, + opensea: { label: 'OpenSea', icon: 'social/opensea_filled', color: 'opensea' }, + facebook: { label: 'Facebook', icon: 'social/facebook_filled', color: 'facebook' }, + medium: { label: 'Medium', icon: 'social/medium_filled', color: 'inherit' }, + reddit: { label: 'Reddit', icon: 'social/reddit_filled', color: 'reddit' }, }; interface Props { @@ -55,7 +47,7 @@ const TokenInfoFieldSocialLink = ({ control, isReadOnly, name }: Props) => { /> - + diff --git a/ui/tokenInstance/TokenInstanceDetails.pw.tsx b/ui/tokenInstance/TokenInstanceDetails.pw.tsx index 11ae3e3a0b..d4d31085e4 100644 --- a/ui/tokenInstance/TokenInstanceDetails.pw.tsx +++ b/ui/tokenInstance/TokenInstanceDetails.pw.tsx @@ -2,6 +2,7 @@ import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as addressMock from 'mocks/address/address'; +import { tokenInfoERC721a } from 'mocks/tokens/tokenInfo'; import * as tokenInstanceMock from 'mocks/tokens/tokenInstance'; import TestApp from 'playwright/TestApp'; import buildApiUrl from 'playwright/utils/buildApiUrl'; @@ -9,10 +10,12 @@ import * as configs from 'playwright/utils/configs'; import TokenInstanceDetails from './TokenInstanceDetails'; -const API_URL_ADDRESS = buildApiUrl('address', { hash: tokenInstanceMock.base.token.address }); +const hash = tokenInfoERC721a.address; + +const API_URL_ADDRESS = buildApiUrl('address', { hash }); const API_URL_TOKEN_TRANSFERS_COUNT = buildApiUrl('token_instance_transfers_count', { id: tokenInstanceMock.unique.id, - hash: tokenInstanceMock.unique.token.address, + hash, }); test('base view +@dark-mode +@mobile', async({ mount, page }) => { @@ -31,7 +34,7 @@ test('base view +@dark-mode +@mobile', async({ mount, page }) => { const component = await mount( - + , ); diff --git a/ui/tokenInstance/TokenInstanceDetails.tsx b/ui/tokenInstance/TokenInstanceDetails.tsx index aae2b1302c..6896f1cc4c 100644 --- a/ui/tokenInstance/TokenInstanceDetails.tsx +++ b/ui/tokenInstance/TokenInstanceDetails.tsx @@ -1,7 +1,7 @@ import { Flex, Grid, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import type { TokenInstance } from 'types/api/token'; +import type { TokenInfo, TokenInstance } from 'types/api/token'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; @@ -18,11 +18,12 @@ import TokenInstanceTransfersCount from './details/TokenInstanceTransfersCount'; interface Props { data?: TokenInstance; + token?: TokenInfo; isLoading?: boolean; scrollRef?: React.RefObject; } -const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { +const TokenInstanceDetails = ({ data, token, scrollRef, isLoading }: Props) => { const handleCounterItemClick = React.useCallback(() => { window.setTimeout(() => { // cannot do scroll instantly, have to wait a little @@ -30,7 +31,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { }, 500); }, [ scrollRef ]); - if (!data) { + if (!data || !token) { return null; } @@ -56,7 +57,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { /> ) } - + { - - + + Token - { sorting?.includes('fiat_value') && } + { sorting?.includes('fiat_value') && } Price - { sorting?.includes('circulating_market_cap') && } + { sorting?.includes('circulating_market_cap') && } On-chain market cap - { sorting?.includes('holder_count') && } + { sorting?.includes('holder_count') && } Holders diff --git a/ui/tokens/TokensTableItem.tsx b/ui/tokens/TokensTableItem.tsx index 937f03ac82..37d3f04dee 100644 --- a/ui/tokens/TokensTableItem.tsx +++ b/ui/tokens/TokensTableItem.tsx @@ -51,7 +51,13 @@ const TokensTableItem = ({ }; return ( - + - + { type } diff --git a/ui/tokens/utils.ts b/ui/tokens/utils.ts index dd4aea8383..804271ccbc 100644 --- a/ui/tokens/utils.ts +++ b/ui/tokens/utils.ts @@ -1,7 +1,5 @@ import type { TokenType } from 'types/api/token'; -import type { TokensSortingField, TokensSortingValue, TokensSorting } from 'types/api/tokens'; - -import type { Query } from 'nextjs-routes'; +import type { TokensSortingValue } from 'types/api/tokens'; import config from 'configs/app'; import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; @@ -29,22 +27,3 @@ const bridgedTokensChainIds = (() => { return feature.chains.map(chain => chain.id); })(); export const getBridgedChainsFilterValue = (getFilterValuesFromQuery).bind(null, bridgedTokensChainIds); - -export const getSortValueFromQuery = (query: Query): TokensSortingValue | undefined => { - if (!query.sort || !query.order) { - return undefined; - } - - const str = query.sort + '-' + query.order; - if (SORT_OPTIONS.map(option => option.id).includes(str)) { - return str as TokensSortingValue; - } -}; - -export const getSortParamsFromValue = (val?: TokensSortingValue): TokensSorting | undefined => { - if (!val) { - return undefined; - } - const sortingChunks = val.split('-') as [ TokensSortingField, TokensSorting['order'] ]; - return { sort: sortingChunks[0], order: sortingChunks[1] }; -}; diff --git a/ui/tx/TxDetails.tsx b/ui/tx/TxDetails.tsx index f29007e79f..f56b8096fc 100644 --- a/ui/tx/TxDetails.tsx +++ b/ui/tx/TxDetails.tsx @@ -3,7 +3,6 @@ import { GridItem, Text, Box, - Icon as ChakraIcon, Link, Spinner, Flex, @@ -22,15 +21,10 @@ import { ZKEVM_L2_TX_STATUSES } from 'types/api/transaction'; import { route } from 'nextjs-routes'; import config from 'configs/app'; -import clockIcon from 'icons/clock.svg'; -import flameIcon from 'icons/flame.svg'; -import errorIcon from 'icons/status/error.svg'; -import successIcon from 'icons/status/success.svg'; import { WEI, WEI_IN_GWEI } from 'lib/consts'; import dayjs from 'lib/date/dayjs'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getConfirmationDuration from 'lib/tx/getConfirmationDuration'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CurrencyValue from 'ui/shared/CurrencyValue'; @@ -42,6 +36,7 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import ZkEvmBatchEntityL2 from 'ui/shared/entities/block/ZkEvmBatchEntityL2'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData'; import RawInputData from 'ui/shared/RawInputData'; import TxStatus from 'ui/shared/statusTag/TxStatus'; @@ -49,7 +44,7 @@ import TextSeparator from 'ui/shared/TextSeparator'; import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import Utilization from 'ui/shared/Utilization/Utilization'; import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; -import TxDetailsActions from 'ui/tx/details/TxDetailsActions'; +import TxDetailsActions from 'ui/tx/details/txDetailsActions/TxDetailsActions'; import TxDetailsFeePerGas from 'ui/tx/details/TxDetailsFeePerGas'; import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice'; import TxDetailsOther from 'ui/tx/details/TxDetailsOther'; @@ -104,19 +99,17 @@ const TxDetails = () => { ...toAddress?.watchlist_names || [], ].map((tag) => { tag.display_name }); - const actionsExist = data.actions && data.actions.length > 0; - const executionSuccessBadge = toAddress?.is_contract && data.result === 'success' ? ( - + ) : null; const executionFailedBadge = toAddress?.is_contract && Boolean(data.status) && data.result !== 'success' ? ( - + ) : null; @@ -219,7 +212,7 @@ const TxDetails = () => { hint="Date & time of transaction inclusion, including length of time for confirmation" isLoading={ isPlaceholderData } > - + { dayjs(data.timestamp).fromNow() } { dayjs(data.timestamp).format('llll') } @@ -248,12 +241,7 @@ const TxDetails = () => { - { actionsExist && ( - <> - - - - ) } + { title="Burnt fees" hint={ `Amount of ${ config.chain.currency.symbol } burned for this transaction. Equals Block Base Fee per Gas * Gas Used` } > - + { flexWrap="wrap" /> - - - + { data.fee.value !== null && ( + + + + ) } { data.gas_limit && ( { + const component = await mount( + + + , + ); + + await expect(component).toHaveScreenshot(); +}); + +const bsInterpretationTest = test.extend({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context: contextWithEnvs(configs.featureEnvs.txInterpretation) as any, +}); + +bsInterpretationTest('with interpretation +@mobile +@dark-mode', async({ mount, page }) => { + await page.route(TX_INTERPRETATION_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(txInterpretation), + })); + + const component = await mount( + + + , + ); + + await expect(component).toHaveScreenshot(); +}); + +bsInterpretationTest('no interpretation', async({ mount, page }) => { + await page.route(TX_INTERPRETATION_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify({ data: { summaries: [] } }), + })); + + const component = await mount( + + + , + ); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/tx/TxSubHeading.tsx b/ui/tx/TxSubHeading.tsx new file mode 100644 index 0000000000..477aeddef6 --- /dev/null +++ b/ui/tx/TxSubHeading.tsx @@ -0,0 +1,54 @@ +import { Box, Flex, Link } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; +import useApiQuery from 'lib/api/useApiQuery'; +import { TX_INTERPRETATION } from 'stubs/txInterpretation'; +import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; +import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import NetworkExplorers from 'ui/shared/NetworkExplorers'; +import { TX_ACTIONS_BLOCK_ID } from 'ui/tx/details/txDetailsActions/TxDetailsActionsWrapper'; +import TxInterpretation from 'ui/tx/interpretation/TxInterpretation'; + +type Props = { + hash?: string; + hasTag: boolean; +} + +const TxSubHeading = ({ hash, hasTag }: Props) => { + const hasInterpretationFeature = config.features.txInterpretation.isEnabled; + + const txInterpretationQuery = useApiQuery('tx_interpretation', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash) && hasInterpretationFeature, + placeholderData: TX_INTERPRETATION, + }, + }); + + const hasInterpretation = hasInterpretationFeature && + (txInterpretationQuery.isPlaceholderData || Boolean(txInterpretationQuery.data?.data.summaries.length)); + + return ( + + { hasInterpretation && ( + + + { !txInterpretationQuery.isPlaceholderData && txInterpretationQuery.data?.data.summaries && txInterpretationQuery.data?.data.summaries.length > 1 && + all actions } + + ) } + { !hasInterpretation && } + + { !hasTag && } + + + + ); +}; + +export default TxSubHeading; diff --git a/ui/tx/__screenshots__/TxDetails.pw.tsx_default_stability-customization-1.png b/ui/tx/__screenshots__/TxDetails.pw.tsx_default_stability-customization-1.png index 22b768b228..7f82ce728f 100644 Binary files a/ui/tx/__screenshots__/TxDetails.pw.tsx_default_stability-customization-1.png and b/ui/tx/__screenshots__/TxDetails.pw.tsx_default_stability-customization-1.png differ diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_dark-color-mode_with-interpretation-mobile-dark-mode-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_dark-color-mode_with-interpretation-mobile-dark-mode-1.png new file mode 100644 index 0000000000..d207a60184 Binary files /dev/null and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_dark-color-mode_with-interpretation-mobile-dark-mode-1.png differ diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-1.png new file mode 100644 index 0000000000..86e1242778 Binary files /dev/null and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-1.png differ diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-mobile-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-mobile-1.png new file mode 100644 index 0000000000..86e1242778 Binary files /dev/null and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-mobile-1.png differ diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_with-interpretation-mobile-dark-mode-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_with-interpretation-mobile-dark-mode-1.png new file mode 100644 index 0000000000..69b55d642c Binary files /dev/null and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_with-interpretation-mobile-dark-mode-1.png differ diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_mobile_no-interpretation-mobile-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_mobile_no-interpretation-mobile-1.png new file mode 100644 index 0000000000..c72dc3aacb Binary files /dev/null and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_mobile_no-interpretation-mobile-1.png differ diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_mobile_with-interpretation-mobile-dark-mode-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_mobile_with-interpretation-mobile-dark-mode-1.png new file mode 100644 index 0000000000..4d0e85260a Binary files /dev/null and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_mobile_with-interpretation-mobile-dark-mode-1.png differ diff --git a/ui/tx/details/TxDetailsFeePerGas.tsx b/ui/tx/details/TxDetailsFeePerGas.tsx index f76736d40a..fa20c03906 100644 --- a/ui/tx/details/TxDetailsFeePerGas.tsx +++ b/ui/tx/details/TxDetailsFeePerGas.tsx @@ -6,13 +6,13 @@ import config from 'configs/app'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; interface Props { - txFee: string; + txFee: string | null; gasUsed: string | null; isLoading?: boolean; } const TxDetailsFeePerGas = ({ txFee, gasUsed, isLoading }: Props) => { - if (!config.UI.views.tx.additionalFields?.fee_per_gas || !gasUsed) { + if (!config.UI.views.tx.additionalFields?.fee_per_gas || !gasUsed || txFee === null) { return null; } diff --git a/ui/tx/details/TxDetailsTokenTransfer.tsx b/ui/tx/details/TxDetailsTokenTransfer.tsx index ca0389c5c0..6a1c71e342 100644 --- a/ui/tx/details/TxDetailsTokenTransfer.tsx +++ b/ui/tx/details/TxDetailsTokenTransfer.tsx @@ -1,12 +1,12 @@ -import { Flex, Icon, chakra } from '@chakra-ui/react'; +import { Flex, chakra } from '@chakra-ui/react'; import React from 'react'; import type { TokenTransfer as TTokenTransfer, Erc20TotalPayload, Erc721TotalPayload, Erc1155TotalPayload } from 'types/api/tokenTransfer'; -import rightArrowIcon from 'icons/arrows/east.svg'; import getCurrencyValue from 'lib/getCurrencyValue'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet'; interface Props { @@ -77,7 +77,7 @@ const TxDetailsTokenTransfer = ({ data }: Props) => { > - + diff --git a/ui/tx/details/TxDetailsTokenTransfers.tsx b/ui/tx/details/TxDetailsTokenTransfers.tsx index 7f07039cd6..fb728c3887 100644 --- a/ui/tx/details/TxDetailsTokenTransfers.tsx +++ b/ui/tx/details/TxDetailsTokenTransfers.tsx @@ -1,12 +1,12 @@ -import { Icon, GridItem, Show, Flex } from '@chakra-ui/react'; +import { GridItem, Show, Flex } from '@chakra-ui/react'; import React from 'react'; import type { TokenTransfer } from 'types/api/tokenTransfer'; import { route } from 'nextjs-routes'; -import tokenIcon from 'icons/token.svg'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; import TxDetailsTokenTransfer from './TxDetailsTokenTransfer'; @@ -62,7 +62,7 @@ const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => { <> - + View all diff --git a/ui/tx/details/TxDetailsAction.tsx b/ui/tx/details/txDetailsActions/TxDetailsAction.tsx similarity index 95% rename from ui/tx/details/TxDetailsAction.tsx rename to ui/tx/details/txDetailsActions/TxDetailsAction.tsx index 5182e36ea2..0c53d1570d 100644 --- a/ui/tx/details/TxDetailsAction.tsx +++ b/ui/tx/details/txDetailsActions/TxDetailsAction.tsx @@ -1,14 +1,14 @@ -import { Flex, Icon, chakra } from '@chakra-ui/react'; +import { Flex, chakra } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import React from 'react'; import type { TxAction, TxActionGeneral } from 'types/api/txAction'; import config from 'configs/app'; -import uniswapIcon from 'icons/uniswap.svg'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { action: TxAction; @@ -88,7 +88,7 @@ const TxDetailsAction = ({ action }: Props) => { { text1 } - + Uniswap V3 diff --git a/ui/tx/details/txDetailsActions/TxDetailsActions.tsx b/ui/tx/details/txDetailsActions/TxDetailsActions.tsx new file mode 100644 index 0000000000..fc0622e2af --- /dev/null +++ b/ui/tx/details/txDetailsActions/TxDetailsActions.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import type { TxAction } from 'types/api/txAction'; + +import config from 'configs/app'; +import TxDetailsActionsInterpretation from 'ui/tx/details/txDetailsActions/TxDetailsActionsInterpretation'; +import TxDetailsActionsRaw from 'ui/tx/details/txDetailsActions/TxDetailsActionsRaw'; + +type Props = { + isTxDataLoading: boolean; + actions?: Array; + hash?: string; +} + +const TxDetailsActions = ({ isTxDataLoading, actions, hash }: Props) => { + if (config.features.txInterpretation.isEnabled) { + return ; + } + + /* if tx interpretation is not configured, show tx actions from tx info */ + if (actions && actions.length > 0) { + return ; + } + + return null; +}; + +export default TxDetailsActions; diff --git a/ui/tx/details/txDetailsActions/TxDetailsActionsInterpretation.tsx b/ui/tx/details/txDetailsActions/TxDetailsActionsInterpretation.tsx new file mode 100644 index 0000000000..ade6ac072a --- /dev/null +++ b/ui/tx/details/txDetailsActions/TxDetailsActionsInterpretation.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import useApiQuery from 'lib/api/useApiQuery'; +import { TX_INTERPRETATION } from 'stubs/txInterpretation'; +import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; +import TxInterpretation from 'ui/tx/interpretation/TxInterpretation'; + +import TxDetailsActionsWrapper from './TxDetailsActionsWrapper'; + +interface Props { + hash?: string; + isTxDataLoading: boolean; +} + +const TxDetailsActionsInterpretation = ({ hash, isTxDataLoading }: Props) => { + const txInterpretationQuery = useApiQuery('tx_interpretation', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash) && !isTxDataLoading, + placeholderData: TX_INTERPRETATION, + refetchOnMount: false, + }, + }); + + const actions = txInterpretationQuery.data?.data.summaries; + + if (!actions || actions.length < 2) { + return null; + } + + return ( + <> + + { actions.map((action, index: number) => ( + + ), + ) } + + + + ); +}; + +export default TxDetailsActionsInterpretation; diff --git a/ui/tx/details/txDetailsActions/TxDetailsActionsRaw.tsx b/ui/tx/details/txDetailsActions/TxDetailsActionsRaw.tsx new file mode 100644 index 0000000000..a024347519 --- /dev/null +++ b/ui/tx/details/txDetailsActions/TxDetailsActionsRaw.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import type { TxAction } from 'types/api/txAction'; + +import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; + +import TxDetailsAction from './TxDetailsAction'; +import TxDetailsActionsWrapper from './TxDetailsActionsWrapper'; + +interface Props { + actions: Array; + isLoading: boolean; +} + +const TxDetailsActionsRaw = ({ actions, isLoading }: Props) => { + return ( + <> + + { actions.map((action, index: number) => ) } + + + + ); +}; + +export default TxDetailsActionsRaw; diff --git a/ui/tx/details/TxDetailsActions.tsx b/ui/tx/details/txDetailsActions/TxDetailsActionsWrapper.tsx similarity index 83% rename from ui/tx/details/TxDetailsActions.tsx rename to ui/tx/details/txDetailsActions/TxDetailsActionsWrapper.tsx index 200e0315fc..c9e963e803 100644 --- a/ui/tx/details/TxDetailsActions.tsx +++ b/ui/tx/details/txDetailsActions/TxDetailsActionsWrapper.tsx @@ -1,19 +1,18 @@ import { Flex, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; -import type { TxAction } from 'types/api/txAction'; - import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; -import TxDetailsAction from './TxDetailsAction'; - const SCROLL_GRADIENT_HEIGHT = 48; -interface Props { - actions: Array; +type Props = { + children: React.ReactNode; + isLoading?: boolean; } -const TxDetailsActions = ({ actions }: Props) => { +export const TX_ACTIONS_BLOCK_ID = 'tx-actions'; + +const TxDetailsActions = ({ children, isLoading }: Props) => { const containerRef = React.useRef(null); const [ hasScroll, setHasScroll ] = React.useState(false); @@ -34,8 +33,10 @@ const TxDetailsActions = ({ actions }: Props) => { hint="Highlighted events of the transaction" note={ hasScroll ? 'Scroll to see more' : undefined } position="relative" + isLoading={ isLoading } > { pr={ hasScroll ? 5 : 0 } pb={ hasScroll ? 10 : 0 } > - { actions.map((action, index: number) => ) } + { children } ); diff --git a/ui/tx/internals/TxInternalsListItem.tsx b/ui/tx/internals/TxInternalsListItem.tsx index 7d416ecef2..df6d73519f 100644 --- a/ui/tx/internals/TxInternalsListItem.tsx +++ b/ui/tx/internals/TxInternalsListItem.tsx @@ -5,10 +5,9 @@ import React from 'react'; import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; -import eastArrowIcon from 'icons/arrows/east.svg'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import IconSvg from 'ui/shared/IconSvg'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TxStatus from 'ui/shared/statusTag/TxStatus'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; @@ -31,7 +30,7 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit: isLoading={ isLoading } width="calc((100% - 48px) / 2)" /> - + { toData && ( To - { sort?.includes('value') && } + { sort?.includes('value') && } Value { config.chain.currency.symbol } - { sort?.includes('gas-limit') && } + { sort?.includes('gas-limit') && } Gas limit { config.chain.currency.symbol } diff --git a/ui/tx/internals/TxInternalsTableItem.tsx b/ui/tx/internals/TxInternalsTableItem.tsx index d15f19fa71..cfa1bca773 100644 --- a/ui/tx/internals/TxInternalsTableItem.tsx +++ b/ui/tx/internals/TxInternalsTableItem.tsx @@ -5,10 +5,9 @@ import React from 'react'; import type { InternalTransaction } from 'types/api/internalTransaction'; import config from 'configs/app'; -import rightArrowIcon from 'icons/arrows/east.svg'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import IconSvg from 'ui/shared/IconSvg'; import TxStatus from 'ui/shared/statusTag/TxStatus'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; @@ -39,7 +38,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: /> - + { toData && ( diff --git a/ui/tx/interpretation/TxInterpretation.tsx b/ui/tx/interpretation/TxInterpretation.tsx new file mode 100644 index 0000000000..293d712977 --- /dev/null +++ b/ui/tx/interpretation/TxInterpretation.tsx @@ -0,0 +1,115 @@ +import { Skeleton, Flex, Text, chakra } from '@chakra-ui/react'; +import BigNumber from 'bignumber.js'; +import React from 'react'; + +import type { TxInterpretationSummary, TxInterpretationVariable } from 'types/api/txInterpretation'; + +import config from 'configs/app'; +import dayjs from 'lib/date/dayjs'; +import * as mixpanel from 'lib/mixpanel/index'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; + +import { extractVariables, getStringChunks, NATIVE_COIN_SYMBOL_VAR_NAME } from './utils'; + +type Props = { + summary?: TxInterpretationSummary; + isLoading?: boolean; + className?: string; +} + +const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretationVariable }) => { + const onAddressClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' }); + }, []); + + const onTokenClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Token click' }); + }, []); + + if (!variable) { + return null; + } + + const { type, value } = variable; + switch (type) { + case 'address': { + return ( + + ); + } + case 'token': + return ( + + ); + case 'currency': { + let numberString = ''; + if (BigNumber(value).isLessThan(0.1)) { + numberString = BigNumber(value).toPrecision(2); + } else if (BigNumber(value).isLessThan(10000)) { + numberString = BigNumber(value).dp(2).toFormat(); + } else if (BigNumber(value).isLessThan(1000000)) { + numberString = BigNumber(value).dividedBy(1000).toFormat(2) + 'K'; + } else { + numberString = BigNumber(value).dividedBy(1000000).toFormat(2) + 'M'; + } + return { numberString + ' ' }; + } + case 'timestamp': + // timestamp is in unix format + return { dayjs(Number(value) * 1000).format('llll') + ' ' }; + case 'string': + default: { + return { value.toString() + ' ' }; + } + } +}; + +const TxInterpretation = ({ summary, isLoading, className }: Props) => { + if (!summary) { + return null; + } + + const template = summary.summary_template; + const variables = summary.summary_template_variables; + + const variablesNames = extractVariables(template); + + const chunks = getStringChunks(template); + + return ( + + + { chunks.map((chunk, index) => { + return ( + + { chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') } + { index < variablesNames.length && ( + variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME ? + { config.chain.currency.symbol + ' ' } : + + ) } + + ); + }) } + + ); +}; + +export default chakra(TxInterpretation); diff --git a/ui/tx/interpretation/utils.test.ts b/ui/tx/interpretation/utils.test.ts new file mode 100644 index 0000000000..c3089594b7 --- /dev/null +++ b/ui/tx/interpretation/utils.test.ts @@ -0,0 +1,13 @@ +import { extractVariables, getStringChunks } from './utils'; + +const template = '{action_type} {source_amount} {native} into {destination_amount} {destination_token}'; + +it('extracts variables names', () => { + const result = extractVariables(template); + expect(result).toEqual([ 'action_type', 'source_amount', 'native', 'destination_amount', 'destination_token' ]); +}); + +it('split string without capturing variables', () => { + const result = getStringChunks(template); + expect(result).toEqual([ '', ' ', ' ', ' into ', ' ', '' ]); +}); diff --git a/ui/tx/interpretation/utils.ts b/ui/tx/interpretation/utils.ts new file mode 100644 index 0000000000..c6ffbb6e42 --- /dev/null +++ b/ui/tx/interpretation/utils.ts @@ -0,0 +1,18 @@ +// we use that regex as a separator when splitting template and dont want to capture variables +// eslint-disable-next-line regexp/no-useless-non-capturing-group +export const VAR_REGEXP = /\{(?:[^}]+)\}/g; + +export const NATIVE_COIN_SYMBOL_VAR_NAME = 'native'; + +export function extractVariables(templateString: string) { + + const matches = templateString.match(VAR_REGEXP); + + const variablesNames = matches ? matches.map(match => match.slice(1, -1)) : []; + + return variablesNames; +} + +export function getStringChunks(template: string) { + return template.split(VAR_REGEXP); +} diff --git a/ui/txs/TxAdditionalInfoContent.tsx b/ui/txs/TxAdditionalInfoContent.tsx index b9e2ed66f9..e16a768439 100644 --- a/ui/txs/TxAdditionalInfoContent.tsx +++ b/ui/txs/TxAdditionalInfoContent.tsx @@ -33,20 +33,24 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => { Additional info { !config.UI.views.tx.hiddenFields?.tx_fee && ( - Transaction fee - { tx.stability_fee ? ( - - ) : ( - - - + { (tx.stability_fee !== undefined || tx.fee.value !== null) && ( + <> + Transaction fee + { tx.stability_fee ? ( + + ) : ( + + + + ) } + ) } ) } diff --git a/ui/txs/TxsContent.tsx b/ui/txs/TxsContent.tsx index fb81ae48b3..101a7ed31c 100644 --- a/ui/txs/TxsContent.tsx +++ b/ui/txs/TxsContent.tsx @@ -2,17 +2,23 @@ import { Box, Show, Hide } from '@chakra-ui/react'; import React from 'react'; import type { AddressFromToFilter } from 'types/api/address'; +import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import useIsMobile from 'lib/hooks/useIsMobile'; import AddressCsvExportLink from 'ui/address/AddressCsvExportLink'; import DataListDisplay from 'ui/shared/DataListDisplay'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; +import getNextSortValue from 'ui/shared/sort/getNextSortValue'; import TxsHeaderMobile from './TxsHeaderMobile'; import TxsListItem from './TxsListItem'; import TxsTable from './TxsTable'; -import useTxsSort from './useTxsSort'; + +const SORT_SEQUENCE: Record> = { + value: [ 'value-desc', 'value-asc', undefined ], + fee: [ 'fee-desc', 'fee-asc', undefined ], +}; type Props = { // eslint-disable-next-line max-len @@ -26,12 +32,17 @@ type Props = { filterValue?: AddressFromToFilter; enableTimeIncrement?: boolean; top?: number; + items?: Array; + isPlaceholderData: boolean; + isError: boolean; + setSorting: (value: TransactionsSortingValue | undefined) => void; + sort: TransactionsSortingValue | undefined; } const TxsContent = ({ + query, filter, filterValue, - query, showBlockInfo = true, showSocketInfo = true, socketInfoAlert, @@ -39,11 +50,20 @@ const TxsContent = ({ currentAddress, enableTimeIncrement, top, + items, + isPlaceholderData, + isError, + setSorting, + sort, }: Props) => { - const { data, isPlaceholderData, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query); const isMobile = useIsMobile(); - const content = data?.items ? ( + const onSortToggle = React.useCallback((field: TransactionsSortingField) => () => { + const value = getNextSortValue(SORT_SEQUENCE, field)(sort); + setSorting(value); + }, [ sort, setSorting ]); + + const content = items ? ( <> @@ -55,7 +75,7 @@ const TxsContent = ({ isLoading={ isPlaceholderData } /> ) } - { data.items.map((tx, index) => ( + { items.map((tx, index) => ( > = [ - { title: 'Default', id: undefined }, - { title: 'Value ascending', id: 'val-asc' }, - { title: 'Value descending', id: 'val-desc' }, - { title: 'Fee ascending', id: 'fee-asc' }, - { title: 'Fee descending', id: 'fee-desc' }, -]; +// import TxsFilters from './TxsFilters'; type Props = { - sorting: TSort; - setSorting: (val: TSort | undefined) => void; + sorting: TransactionsSortingValue | undefined; + setSorting: (val: TransactionsSortingValue | undefined) => void; paginationProps: PaginationParams; className?: string; showPagination?: boolean; diff --git a/ui/txs/TxsListItem.tsx b/ui/txs/TxsListItem.tsx index 4d1b72eb69..84ac614f50 100644 --- a/ui/txs/TxsListItem.tsx +++ b/ui/txs/TxsListItem.tsx @@ -1,6 +1,5 @@ import { HStack, - Box, Flex, Skeleton, } from '@chakra-ui/react'; @@ -9,14 +8,13 @@ import React from 'react'; import type { Transaction } from 'types/api/transaction'; import config from 'configs/app'; -import rightArrowIcon from 'icons/arrows/east.svg'; import getValueWithUnit from 'lib/getValueWithUnit'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import { space } from 'lib/html-entities'; -import Icon from 'ui/shared/chakra/Icon'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import IconSvg from 'ui/shared/IconSvg'; import InOutTag from 'ui/shared/InOutTag'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import TxStatus from 'ui/shared/statusTag/TxStatus'; @@ -102,14 +100,14 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI /> { (isIn || isOut) ? : ( - - - + ) } { dataTo ? ( - Fee - { tx.stability_fee ? ( - - ) : ( - - { getValueWithUnit(tx.fee.value).toFormat() } - { config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` } - + { (tx.stability_fee !== undefined || tx.fee.value !== null) && ( + <> + Fee + { tx.stability_fee ? ( + + ) : ( + + { getValueWithUnit(tx.fee.value || 0).toFormat() } + { config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` } + + ) } + ) } ) } diff --git a/ui/txs/TxsTable.tsx b/ui/txs/TxsTable.tsx index 7af0f4c013..c1946320fd 100644 --- a/ui/txs/TxsTable.tsx +++ b/ui/txs/TxsTable.tsx @@ -1,12 +1,11 @@ -import { Link, Table, Tbody, Tr, Th, Icon, Show, Hide } from '@chakra-ui/react'; +import { Link, Table, Tbody, Tr, Th, Show, Hide } from '@chakra-ui/react'; import { AnimatePresence } from 'framer-motion'; import React from 'react'; -import type { Transaction } from 'types/api/transaction'; -import type { Sort } from 'types/client/txs-sort'; +import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import config from 'configs/app'; -import rightArrowIcon from 'icons/arrows/east.svg'; +import IconSvg from 'ui/shared/IconSvg'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import TheadSticky from 'ui/shared/TheadSticky'; @@ -14,8 +13,8 @@ import TxsTableItem from './TxsTableItem'; type Props = { txs: Array; - sort: (field: 'val' | 'fee') => () => void; - sorting?: Sort; + sort: (field: TransactionsSortingField) => () => void; + sorting?: TransactionsSortingValue; top: number; showBlockInfo: boolean; showSocketInfo: boolean; @@ -58,9 +57,9 @@ const TxsTable = ({ { !config.UI.views.tx.hiddenFields?.value && ( - - { sorting === 'val-asc' && } - { sorting === 'val-desc' && } + + { sorting === 'value-asc' && } + { sorting === 'value-desc' && } { `Value ${ config.chain.currency.symbol }` } @@ -68,8 +67,8 @@ const TxsTable = ({ { !config.UI.views.tx.hiddenFields?.tx_fee && ( - { sorting === 'fee-asc' && } - { sorting === 'fee-desc' && } + { sorting === 'fee-asc' && } + { sorting === 'fee-desc' && } { `Fee${ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` }` } diff --git a/ui/txs/TxsTableItem.tsx b/ui/txs/TxsTableItem.tsx index 42443476ef..eb95016674 100644 --- a/ui/txs/TxsTableItem.tsx +++ b/ui/txs/TxsTableItem.tsx @@ -14,14 +14,13 @@ import React from 'react'; import type { Transaction } from 'types/api/transaction'; import config from 'configs/app'; -import rightArrowIcon from 'icons/arrows/east.svg'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; -import Icon from 'ui/shared/chakra/Icon'; import Tag from 'ui/shared/chakra/Tag'; import CurrencyValue from 'ui/shared/CurrencyValue'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; +import IconSvg from 'ui/shared/IconSvg'; import InOutTag from 'ui/shared/InOutTag'; import TxStatus from 'ui/shared/statusTag/TxStatus'; import TxFeeStability from 'ui/shared/tx/TxFeeStability'; @@ -129,7 +128,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, { (isIn || isOut) ? : ( - + ) } @@ -142,8 +141,8 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, { (isIn || isOut) ? : ( - + { /* eslint-disable-next-line no-nested-ternary */ } { tx.stability_fee ? ( ) : ( - + tx.fee.value ? : '-' ) } ) } diff --git a/ui/txs/TxsWatchlist.tsx b/ui/txs/TxsWatchlist.tsx index 7f0567bffc..67edde53d2 100644 --- a/ui/txs/TxsWatchlist.tsx +++ b/ui/txs/TxsWatchlist.tsx @@ -2,7 +2,7 @@ import React from 'react'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; type Props = { query: QueryWithPagesResult<'txs_watchlist'>; @@ -10,7 +10,7 @@ type Props = { const TxsWatchlist = ({ query }: Props) => { useRedirectForInvalidAuthToken(); - return ; + return ; }; export default TxsWatchlist; diff --git a/ui/txs/TxsWithAPISorting.tsx b/ui/txs/TxsWithAPISorting.tsx new file mode 100644 index 0000000000..140c8f0a99 --- /dev/null +++ b/ui/txs/TxsWithAPISorting.tsx @@ -0,0 +1,68 @@ +import React from 'react'; + +import type { AddressFromToFilter } from 'types/api/address'; +import type { TransactionsSortingValue } from 'types/api/transaction'; + +import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; +import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; + +import TxsContent from './TxsContent'; + +type Props = { + // eslint-disable-next-line max-len + query: QueryWithPagesResult<'address_txs'>; + showBlockInfo?: boolean; + showSocketInfo?: boolean; + socketInfoAlert?: string; + socketInfoNum?: number; + currentAddress?: string; + filter?: React.ReactNode; + filterValue?: AddressFromToFilter; + enableTimeIncrement?: boolean; + top?: number; + sorting: TransactionsSortingValue | undefined; + setSort: (value?: TransactionsSortingValue) => void; +} + +const TxsWithAPISorting = ({ + filter, + filterValue, + query, + showBlockInfo = true, + showSocketInfo = true, + socketInfoAlert, + socketInfoNum, + currentAddress, + enableTimeIncrement, + top, + sorting, + setSort, +}: Props) => { + + const handleSortChange = React.useCallback((value?: TransactionsSortingValue) => { + setSort(value); + query.onSortingChange(getSortParamsFromValue(value)); + }, [ setSort, query ]); + + return ( + + ); +}; + +export default TxsWithAPISorting; diff --git a/ui/txs/TxsWithFrontendSorting.tsx b/ui/txs/TxsWithFrontendSorting.tsx new file mode 100644 index 0000000000..913a7fb8a4 --- /dev/null +++ b/ui/txs/TxsWithFrontendSorting.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import type { AddressFromToFilter } from 'types/api/address'; + +import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; + +import TxsContent from './TxsContent'; +import useTxsSort from './useTxsSort'; + +type Props = { + // eslint-disable-next-line max-len + query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>; + showBlockInfo?: boolean; + showSocketInfo?: boolean; + socketInfoAlert?: string; + socketInfoNum?: number; + currentAddress?: string; + filter?: React.ReactNode; + filterValue?: AddressFromToFilter; + enableTimeIncrement?: boolean; + top?: number; +} + +const TxsWithFrontendSorting = ({ + filter, + filterValue, + query, + showBlockInfo = true, + showSocketInfo = true, + socketInfoAlert, + socketInfoNum, + currentAddress, + enableTimeIncrement, + top, +}: Props) => { + const { data, isPlaceholderData, isError, setSortByValue, sorting } = useTxsSort(query); + + return ( + + ); +}; + +export default TxsWithFrontendSorting; diff --git a/ui/txs/useTxsSort.tsx b/ui/txs/useTxsSort.tsx index 3a77967507..bee6eefddd 100644 --- a/ui/txs/useTxsSort.tsx +++ b/ui/txs/useTxsSort.tsx @@ -1,78 +1,71 @@ import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; -import type { TxsResponse } from 'types/api/transaction'; -import type { Sort } from 'types/client/txs-sort'; +import type { Transaction, TransactionsSortingValue, TxsResponse } from 'types/api/transaction'; import type { ResourceError } from 'lib/api/resources'; +import compareBns from 'lib/bigint/compareBns'; import * as cookies from 'lib/cookies'; -import sortTxs from 'lib/tx/sortTxs'; +import type { Option } from 'ui/shared/sort/Sort'; + +export const SORT_OPTIONS: Array> = [ + { title: 'Default', id: undefined }, + { title: 'Value ascending', id: 'value-asc' }, + { title: 'Value descending', id: 'value-desc' }, + { title: 'Fee ascending', id: 'fee-asc' }, + { title: 'Fee descending', id: 'fee-desc' }, +]; + +type SortingValue = TransactionsSortingValue | undefined; type HookResult = UseQueryResult> & { - sorting: Sort; - setSortByField: (field: 'val' | 'fee') => () => void; - setSortByValue: (value: Sort | undefined) => void; + sorting: SortingValue; + setSortByValue: (value: SortingValue) => void; } +const sortTxs = (sorting: SortingValue) => (tx1: Transaction, tx2: Transaction) => { + switch (sorting) { + case 'value-desc': + return compareBns(tx1.value, tx2.value); + case 'value-asc': + return compareBns(tx2.value, tx1.value); + case 'fee-desc': + return compareBns(tx1.fee.value || 0, tx2.fee.value || 0); + case 'fee-asc': + return compareBns(tx2.fee.value || 0, tx1.fee.value || 0); + default: + return 0; + } +}; + export default function useTxsSort( queryResult: UseQueryResult>, ): HookResult { - const [ sorting, setSorting ] = React.useState(cookies.get(cookies.NAMES.TXS_SORT) as Sort); - - const setSortByField = React.useCallback((field: 'val' | 'fee') => () => { - if (queryResult.isPlaceholderData) { - return; - } - - setSorting((prevVal) => { - let newVal: Sort = ''; - if (field === 'val') { - if (prevVal === 'val-asc') { - newVal = ''; - } else if (prevVal === 'val-desc') { - newVal = 'val-asc'; - } else { - newVal = 'val-desc'; - } - } - if (field === 'fee') { - if (prevVal === 'fee-asc') { - newVal = ''; - } else if (prevVal === 'fee-desc') { - newVal = 'fee-asc'; - } else { - newVal = 'fee-desc'; - } - } - cookies.set(cookies.NAMES.TXS_SORT, newVal); - return newVal; - }); - }, [ queryResult.isPlaceholderData ]); + const [ sorting, setSorting ] = React.useState(cookies.get(cookies.NAMES.TXS_SORT) as SortingValue); - const setSortByValue = React.useCallback((value: Sort | undefined) => { - setSorting((prevVal: Sort) => { - let newVal: Sort = ''; + const setSortByValue = React.useCallback((value: SortingValue) => { + setSorting((prevVal: SortingValue) => { + let newVal: SortingValue = undefined; if (value !== prevVal) { - newVal = value as Sort; + newVal = value as SortingValue; } - cookies.set(cookies.NAMES.TXS_SORT, newVal); + cookies.set(cookies.NAMES.TXS_SORT, newVal ? newVal : ''); return newVal; }); }, []); return React.useMemo(() => { if (queryResult.isError || queryResult.isPending) { - return { ...queryResult, setSortByField, setSortByValue, sorting }; + return { ...queryResult, setSortByValue, sorting }; } return { ...queryResult, data: { ...queryResult.data, items: queryResult.data.items.slice().sort(sortTxs(sorting)) }, - setSortByField, setSortByValue, sorting, }; - }, [ queryResult, setSortByField, setSortByValue, sorting ]); + }, [ queryResult, setSortByValue, sorting ]); } diff --git a/ui/verifiedAddresses/VerifiedAddressesListItem.tsx b/ui/verifiedAddresses/VerifiedAddressesListItem.tsx index f3706a8209..c87e885c91 100644 --- a/ui/verifiedAddresses/VerifiedAddressesListItem.tsx +++ b/ui/verifiedAddresses/VerifiedAddressesListItem.tsx @@ -3,11 +3,10 @@ import React from 'react'; import type { TokenInfoApplication, VerifiedAddress } from 'types/api/account'; -import editIcon from 'icons/edit.svg'; import dayjs from 'lib/date/dayjs'; -import Icon from 'ui/shared/chakra/Icon'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; import VerifiedAddressesStatus from './VerifiedAddressesStatus'; @@ -71,7 +70,7 @@ const VerifiedAddressesListItem = ({ item, application, onAdd, onEdit, isLoading borderRadius="none" flexShrink={ 0 } onClick={ handleEditClick } - icon={ } + icon={ } /> diff --git a/ui/verifiedAddresses/VerifiedAddressesTableItem.tsx b/ui/verifiedAddresses/VerifiedAddressesTableItem.tsx index 96787d4702..7efcaa2e91 100644 --- a/ui/verifiedAddresses/VerifiedAddressesTableItem.tsx +++ b/ui/verifiedAddresses/VerifiedAddressesTableItem.tsx @@ -3,11 +3,10 @@ import React from 'react'; import type { TokenInfoApplication, VerifiedAddress } from 'types/api/account'; -import editIcon from 'icons/edit.svg'; import dayjs from 'lib/date/dayjs'; -import Icon from 'ui/shared/chakra/Icon'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; import VerifiedAddressesStatus from './VerifiedAddressesStatus'; @@ -87,7 +86,7 @@ const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit, isLoadin borderRadius="none" flexShrink={ 0 } onClick={ handleEditClick } - icon={ } + icon={ } /> ) : null } diff --git a/ui/verifiedContracts/VerifiedContractsListItem.tsx b/ui/verifiedContracts/VerifiedContractsListItem.tsx index 28c14c1254..ec8931c286 100644 --- a/ui/verifiedContracts/VerifiedContractsListItem.tsx +++ b/ui/verifiedContracts/VerifiedContractsListItem.tsx @@ -5,14 +5,11 @@ import React from 'react'; import type { VerifiedContract } from 'types/api/contracts'; import config from 'configs/app'; -import iconCheck from 'icons/check.svg'; -import iconCross from 'icons/cross.svg'; -import iconSuccess from 'icons/status/success.svg'; import dayjs from 'lib/date/dayjs'; -import Icon from 'ui/shared/chakra/Icon'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import HashStringShorten from 'ui/shared/HashStringShorten'; +import IconSvg from 'ui/shared/IconSvg'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; interface Props { @@ -61,19 +58,19 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => { Optimization { data.optimization_enabled ? - : - } + : + } Constructor args { data.has_constructor_args ? - : - } + : + } Verified - + { dayjs(data.verified_at).fromNow() } diff --git a/ui/verifiedContracts/VerifiedContractsTable.tsx b/ui/verifiedContracts/VerifiedContractsTable.tsx index e485616dd4..8d19f5af6b 100644 --- a/ui/verifiedContracts/VerifiedContractsTable.tsx +++ b/ui/verifiedContracts/VerifiedContractsTable.tsx @@ -1,24 +1,31 @@ -import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react'; +import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react'; import React from 'react'; import type { VerifiedContract } from 'types/api/contracts'; +import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts'; import config from 'configs/app'; -import arrowIcon from 'icons/arrows/east.svg'; +import IconSvg from 'ui/shared/IconSvg'; +import getNextSortValue from 'ui/shared/sort/getNextSortValue'; import { default as Thead } from 'ui/shared/TheadSticky'; +import { SORT_SEQUENCE } from 'ui/verifiedContracts/utils'; -import type { Sort, SortField } from './utils'; import VerifiedContractsTableItem from './VerifiedContractsTableItem'; interface Props { data: Array; - sort: Sort | undefined; - onSortToggle: (field: SortField) => () => void; + sort: VerifiedContractsSortingValue | undefined; + setSorting: (val: VerifiedContractsSortingValue | undefined) => void; isLoading?: boolean; } -const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) => { - const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; +const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => { + const sortIconTransform = sort?.includes('asc' as VerifiedContractsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)'; + + const onSortToggle = React.useCallback((field: VerifiedContractsSortingField) => () => { + const value = getNextSortValue(SORT_SEQUENCE, field)(sort); + setSorting(value); + }, [ sort, setSorting ]); return ( @@ -27,13 +34,13 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) diff --git a/ui/verifiedContracts/VerifiedContractsTableItem.tsx b/ui/verifiedContracts/VerifiedContractsTableItem.tsx index 50caee59ec..3a528dd6b9 100644 --- a/ui/verifiedContracts/VerifiedContractsTableItem.tsx +++ b/ui/verifiedContracts/VerifiedContractsTableItem.tsx @@ -5,14 +5,11 @@ import React from 'react'; import type { VerifiedContract } from 'types/api/contracts'; import config from 'configs/app'; -import iconCheck from 'icons/check.svg'; -import iconCross from 'icons/cross.svg'; -import iconSuccess from 'icons/status/success.svg'; import dayjs from 'lib/date/dayjs'; -import Icon from 'ui/shared/chakra/Icon'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import HashStringShorten from 'ui/shared/HashStringShorten'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { data: VerifiedContract; @@ -63,21 +60,21 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => { { data.optimization_enabled ? - : - } + : + } { data.has_constructor_args ? - : - } + : + }
Contract - { sort?.includes('balance') && } + { sort?.includes('balance') && } Balance { config.chain.currency.symbol } - - { sort?.includes('txs') && } + + { sort?.includes('txs_count') && } Txs - + { dayjs(data.verified_at).fromNow() } diff --git a/ui/verifiedContracts/utils.ts b/ui/verifiedContracts/utils.ts index 71325c67be..caf609899f 100644 --- a/ui/verifiedContracts/utils.ts +++ b/ui/verifiedContracts/utils.ts @@ -1,42 +1,16 @@ -import type { VerifiedContract } from 'types/api/contracts'; +import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts'; -import compareBns from 'lib/bigint/compareBns'; -import { default as getNextSortValueShared } from 'ui/shared/sort/getNextSortValue'; import type { Option } from 'ui/shared/sort/Sort'; -export type SortField = 'balance' | 'txs'; -export type Sort = `${ SortField }-asc` | `${ SortField }-desc`; - -export const SORT_OPTIONS: Array> = [ +export const SORT_OPTIONS: Array> = [ { title: 'Default', id: undefined }, { title: 'Balance descending', id: 'balance-desc' }, { title: 'Balance ascending', id: 'balance-asc' }, - { title: 'Txs count descending', id: 'txs-desc' }, - { title: 'Txs count ascending', id: 'txs-asc' }, + { title: 'Txs count descending', id: 'txs_count-desc' }, + { title: 'Txs count ascending', id: 'txs_count-asc' }, ]; -const SORT_SEQUENCE: Record> = { +export const SORT_SEQUENCE: Record> = { balance: [ 'balance-desc', 'balance-asc', undefined ], - txs: [ 'txs-desc', 'txs-asc', undefined ], -}; - -export const getNextSortValue = (getNextSortValueShared).bind(undefined, SORT_SEQUENCE); - -export const sortFn = (sort: Sort | undefined) => (a: VerifiedContract, b: VerifiedContract) => { - switch (sort) { - case 'balance-asc': - case 'balance-desc': { - const result = compareBns(b.coin_balance, a.coin_balance) * (sort.includes('desc') ? 1 : -1); - return a.coin_balance === b.coin_balance ? 0 : result; - } - - case 'txs-asc': - case 'txs-desc': { - const result = ((a.tx_count || 0) > (b.tx_count || 0) ? -1 : 1) * (sort.includes('desc') ? 1 : -1); - return a.tx_count === b.tx_count ? 0 : result; - } - - default: - return 0; - } + txs_count: [ 'txs_count-desc', 'txs_count-asc', undefined ], }; diff --git a/ui/watchlist/WatchlistTable/WatchListAddressItem.tsx b/ui/watchlist/WatchlistTable/WatchListAddressItem.tsx index e3e68c130e..b5194082da 100644 --- a/ui/watchlist/WatchlistTable/WatchListAddressItem.tsx +++ b/ui/watchlist/WatchlistTable/WatchListAddressItem.tsx @@ -5,14 +5,12 @@ import React from 'react'; import type { WatchlistAddress } from 'types/api/account'; import config from 'configs/app'; -import TokensIcon from 'icons/tokens.svg'; -import WalletIcon from 'icons/wallet.svg'; import getCurrencyValue from 'lib/getCurrencyValue'; import { nbsp } from 'lib/html-entities'; -import Icon from 'ui/shared/chakra/Icon'; import CurrencyValue from 'ui/shared/CurrencyValue'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isLoading?: boolean }) => { const nativeTokenData = React.useMemo(() => ({ @@ -51,7 +49,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL { item.tokens_count && ( - + { `Tokens:${ nbsp }` + item.tokens_count + (item.tokens_overflow ? '+' : '') } { `${ nbsp }($${ BigNumber(item.tokens_fiat_value).toFormat(2) })` } @@ -60,7 +58,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL ) } { item.tokens_fiat_value && ( - + { `Net worth:${ nbsp }` } { diff --git a/ui/watchlist/WatchlistTable/WatchListItem.tsx b/ui/watchlist/WatchlistTable/WatchListItem.tsx index 68e1fe6943..571306dae2 100644 --- a/ui/watchlist/WatchlistTable/WatchListItem.tsx +++ b/ui/watchlist/WatchlistTable/WatchListItem.tsx @@ -76,7 +76,7 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick }: Props) = }, onSuccess: () => { setSwitchDisabled(false); - showNotificationToast(!notificationEnabled); + showNotificationToast(notificationEnabled); }, }); diff --git a/ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails.tsx b/ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails.tsx index 103e9c3fc2..3d2b767863 100644 --- a/ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails.tsx +++ b/ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails.tsx @@ -8,16 +8,15 @@ import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2TxnBatches'; import { route } from 'nextjs-routes'; -import clockIcon from 'icons/clock.svg'; import type { ResourceError } from 'lib/api/resources'; import dayjs from 'lib/date/dayjs'; -import Icon from 'ui/shared/chakra/Icon'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; +import IconSvg from 'ui/shared/IconSvg'; import LinkInternal from 'ui/shared/LinkInternal'; import PrevNext from 'ui/shared/PrevNext'; import TextSeparator from 'ui/shared/TextSeparator'; @@ -92,7 +91,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => { title="Timestamp" isLoading={ isPlaceholderData } > - + { dayjs(data.timestamp).fromNow() } diff --git a/yarn.lock b/yarn.lock index d6fc51bce7..af49821c77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@adraffy/ens-normalize@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.0.tgz#223572538f6bea336750039bb43a4016dcc8182d" @@ -29,6 +34,14 @@ dependencies: "@babel/highlight" "^7.18.6" +"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" @@ -81,16 +94,7 @@ json5 "^2.2.2" semver "^6.3.0" -"@babel/generator@^7.19.4": - version "7.19.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.5.tgz#da3f4b301c8086717eee9cab14da91b1fa5dcca7" - integrity sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg== - dependencies: - "@babel/types" "^7.19.4" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.7.2": +"@babel/generator@^7.20.2", "@babel/generator@^7.7.2": version "7.20.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== @@ -99,7 +103,7 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/generator@^7.22.0", "@babel/generator@^7.22.3": +"@babel/generator@^7.22.0": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e" integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A== @@ -109,6 +113,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -188,6 +202,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" @@ -203,13 +222,13 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" -"@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -218,6 +237,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-member-expression-to-functions@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" @@ -333,6 +359,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-string-parser@^7.19.4": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" @@ -343,11 +376,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" @@ -395,26 +438,35 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2": +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.2": version "7.20.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== -"@babel/parser@^7.18.10", "@babel/parser@^7.19.4": +"@babel/parser@^7.18.10": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.4.tgz#03c4339d2b8971eb3beca5252bafd9b9f79db3dc" integrity sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA== -"@babel/parser@^7.20.7": - version "7.21.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.1.tgz#a8f81ee2fe872af23faea4b17a08fcc869de7bcc" - integrity sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg== - -"@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4": +"@babel/parser@^7.21.9", "@babel/parser@^7.22.0": version "7.22.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32" integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== +"@babel/parser@^7.22.15", "@babel/parser@^7.23.5", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1171,6 +1223,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.23.5": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" + integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.8.4": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" @@ -1187,15 +1246,6 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/template@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@babel/template@^7.21.9": version "7.21.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" @@ -1205,52 +1255,29 @@ "@babel/parser" "^7.21.9" "@babel/types" "^7.21.5" -"@babel/traverse@^7.19.0": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.4.tgz#f117820e18b1e59448a6c1fa9d0ff08f7ac459a8" - integrity sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.4" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.4" - "@babel/types" "^7.19.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.7.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.22.1": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0" - integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.22.3" - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.22.4" - "@babel/types" "^7.22.4" - debug "^4.1.0" +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.22.1", "@babel/traverse@^7.7.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" + integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" globals "^11.1.0" "@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": @@ -1262,7 +1289,7 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.19.4": +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== @@ -1271,16 +1298,7 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.20.7", "@babel/types@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819" - integrity sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4": +"@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3": version "7.22.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071" integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA== @@ -1289,6 +1307,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2105,6 +2132,23 @@ resolved "https://registry.yarnpkg.com/@chakra-ui/visually-hidden/-/visually-hidden-2.0.15.tgz#60df64e0ab97d95fee4e6c61ccabd15fd5ace398" integrity sha512-WWULIiucYRBIewHKFA7BssQ2ABLHLVd9lrUo3N3SZgR0u4ZRDDVEUNOy+r+9ruDze8+36dGbN9wsN1IdELtdOw== +"@clack/core@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@clack/core/-/core-0.3.3.tgz#233ccebf779aa5a66fc68ee48df5e58cd226fd94" + integrity sha512-5ZGyb75BUBjlll6eOa1m/IZBxwk91dooBWhPSL67sWcLS0zt9SnswRL0l26TVdBhb0wnWORRxUn//uH6n4z7+A== + dependencies: + picocolors "^1.0.0" + sisteransi "^1.0.5" + +"@clack/prompts@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@clack/prompts/-/prompts-0.7.0.tgz#6aaef48ea803d91cce12bc80811cfcb8de2e75ea" + integrity sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA== + dependencies: + "@clack/core" "^0.3.3" + picocolors "^1.0.0" + sisteransi "^1.0.5" + "@coinbase/wallet-sdk@^3.6.6": version "3.6.6" resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.6.6.tgz#4a0758fe0fe0ba3ed7e33b5bb6eb094ff8bd6c98" @@ -2429,6 +2473,348 @@ ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.2": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@fastify/busboy@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" @@ -2581,6 +2967,23 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2821,6 +3224,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -2845,16 +3253,16 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@ledgerhq/connect-kit-loader@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/connect-kit-loader/-/connect-kit-loader-1.1.0.tgz#10343b78ef13436818bf3453568a559c0eeb9d48" - integrity sha512-HUy12FEczoWY2FPubnsm1uOA8tkVWc0j90i47suThV3C9NL2xx69ZAIEU3Ytzs2bwLek9S1Q2S1VQJvA+3Ygkg== - "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9" integrity sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ== +"@lit-labs/ssr-dom-shim@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" + integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g== + "@lit/reactive-element@^1.3.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.5.0.tgz#344cc33f2d1491d094b8ef824a9fe31d150d2375" @@ -2867,6 +3275,13 @@ dependencies: "@lit-labs/ssr-dom-shim" "^1.0.0" +"@lit/reactive-element@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.2.tgz#779ae9d265407daaf7737cb892df5ec2a86e22a0" + integrity sha512-SVOwLAWUQg3Ji1egtOt1UiFe4zdDpnWHyc5qctSceJ5XIu0Uc76YmGpIjZgx9YJ0XtdW0Jm507sDvjOu+HnB8w== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.1.2" + "@metamask/object-multiplex@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-1.2.0.tgz#38fc15c142f61939391e1b9a8eed679696c7e4f4" @@ -3148,6 +3563,13 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@1.2.0", "@noble/curves@~1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/ed25519@^1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724" @@ -3163,15 +3585,20 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.3.2", "@noble/hashes@~1.3.1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@^1.1.2": version "1.1.5" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ== -"@noble/hashes@~1.3.1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" - integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== "@noble/secp256k1@^1.6.3": version "1.7.1" @@ -4149,6 +4576,103 @@ dependencies: "@opentelemetry/core" "^1.1.0" +"@parcel/watcher-android-arm64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.3.0.tgz#d82e74bb564ebd4d8a88791d273a3d2bd61e27ab" + integrity sha512-f4o9eA3dgk0XRT3XhB0UWpWpLnKgrh1IwNJKJ7UJek7eTYccQ8LR7XUWFKqw6aEq5KUNlCcGvSzKqSX/vtWVVA== + +"@parcel/watcher-darwin-arm64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.3.0.tgz#c9cd03f8f233d512fcfc873d5b4e23f1569a82ad" + integrity sha512-mKY+oijI4ahBMc/GygVGvEdOq0L4DxhYgwQqYAz/7yPzuGi79oXrZG52WdpGA1wLBPrYb0T8uBaGFo7I6rvSKw== + +"@parcel/watcher-darwin-x64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.3.0.tgz#83c902994a2a49b9e1ab5050dba24876fdc2c219" + integrity sha512-20oBj8LcEOnLE3mgpy6zuOq8AplPu9NcSSSfyVKgfOhNAc4eF4ob3ldj0xWjGGbOF7Dcy1Tvm6ytvgdjlfUeow== + +"@parcel/watcher-freebsd-x64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.3.0.tgz#7a0f4593a887e2752b706aff2dae509aef430cf6" + integrity sha512-7LftKlaHunueAEiojhCn+Ef2CTXWsLgTl4hq0pkhkTBFI3ssj2bJXmH2L67mKpiAD5dz66JYk4zS66qzdnIOgw== + +"@parcel/watcher-linux-arm-glibc@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.3.0.tgz#3fc90c3ebe67de3648ed2f138068722f9b1d47da" + integrity sha512-1apPw5cD2xBv1XIHPUlq0cO6iAaEUQ3BcY0ysSyD9Kuyw4MoWm1DV+W9mneWI+1g6OeP6dhikiFE6BlU+AToTQ== + +"@parcel/watcher-linux-arm64-glibc@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.3.0.tgz#f7bbbf2497d85fd11e4c9e9c26ace8f10ea9bcbc" + integrity sha512-mQ0gBSQEiq1k/MMkgcSB0Ic47UORZBmWoAWlMrTW6nbAGoLZP+h7AtUM7H3oDu34TBFFvjy4JCGP43JlylkTQA== + +"@parcel/watcher-linux-arm64-musl@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.3.0.tgz#de131a9fcbe1fa0854e9cbf4c55bed3b35bcff43" + integrity sha512-LXZAExpepJew0Gp8ZkJ+xDZaTQjLHv48h0p0Vw2VMFQ8A+RKrAvpFuPVCVwKJCr5SE+zvaG+Etg56qXvTDIedw== + +"@parcel/watcher-linux-x64-glibc@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.3.0.tgz#193dd1c798003cdb5a1e59470ff26300f418a943" + integrity sha512-P7Wo91lKSeSgMTtG7CnBS6WrA5otr1K7shhSjKHNePVmfBHDoAOHYRXgUmhiNfbcGk0uMCHVcdbfxtuiZCHVow== + +"@parcel/watcher-linux-x64-musl@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.3.0.tgz#6dbdb86d96e955ab0fe4a4b60734ec0025a689dd" + integrity sha512-+kiRE1JIq8QdxzwoYY+wzBs9YbJ34guBweTK8nlzLKimn5EQ2b2FSC+tAOpq302BuIMjyuUGvBiUhEcLIGMQ5g== + +"@parcel/watcher-wasm@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-wasm/-/watcher-wasm-2.3.0.tgz#73b66c6fbd2a3326ae86a1ec77eab7139d0dd725" + integrity sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA== + dependencies: + is-glob "^4.0.3" + micromatch "^4.0.5" + napi-wasm "^1.1.0" + +"@parcel/watcher-win32-arm64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.3.0.tgz#59da26a431da946e6c74fa6b0f30b120ea6650b6" + integrity sha512-35gXCnaz1AqIXpG42evcoP2+sNL62gZTMZne3IackM+6QlfMcJLy3DrjuL6Iks7Czpd3j4xRBzez3ADCj1l7Aw== + +"@parcel/watcher-win32-ia32@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.3.0.tgz#3ee6a18b08929cd3b788e8cc9547fd9a540c013a" + integrity sha512-FJS/IBQHhRpZ6PiCjFt1UAcPr0YmCLHRbTc00IBTrelEjlmmgIVLeOx4MSXzx2HFEy5Jo5YdhGpxCuqCyDJ5ow== + +"@parcel/watcher-win32-x64@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.3.0.tgz#14e7246289861acc589fd608de39fe5d8b4bb0a7" + integrity sha512-dLx+0XRdMnVI62kU3wbXvbIRhLck4aE28bIGKbRGS7BJNt54IIj9+c/Dkqb+7DJEbHUZAX1bwaoM8PqVlHJmCA== + +"@parcel/watcher@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.3.0.tgz#803517abbc3981a1a1221791d9f59dc0590d50f9" + integrity sha512-pW7QaFiL11O0BphO+bq3MgqeX/INAk9jgBldVDYjlQPO4VddoZnF22TcF9onMhnLVHuNqBJeRf+Fj7eezi/+rQ== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.3.0" + "@parcel/watcher-darwin-arm64" "2.3.0" + "@parcel/watcher-darwin-x64" "2.3.0" + "@parcel/watcher-freebsd-x64" "2.3.0" + "@parcel/watcher-linux-arm-glibc" "2.3.0" + "@parcel/watcher-linux-arm64-glibc" "2.3.0" + "@parcel/watcher-linux-arm64-musl" "2.3.0" + "@parcel/watcher-linux-x64-glibc" "2.3.0" + "@parcel/watcher-linux-x64-musl" "2.3.0" + "@parcel/watcher-win32-arm64" "2.3.0" + "@parcel/watcher-win32-ia32" "2.3.0" + "@parcel/watcher-win32-x64" "2.3.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/utils@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" @@ -4416,18 +4940,18 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== -"@safe-global/safe-apps-provider@^0.17.1": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-provider/-/safe-apps-provider-0.17.1.tgz#72df2a66be5343940ed505efe594ed3b0f2f7015" - integrity sha512-lYfRqrbbK1aKU1/UGkYWc/X7PgySYcumXKc5FB2uuwAs2Ghj8uETuW5BrwPqyjBknRxutFbTv+gth/JzjxAhdQ== +"@safe-global/safe-apps-provider@^0.18.1": + version "0.18.1" + resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.1.tgz#287b5a1e2ef3be630dacde54279409df3ced8202" + integrity sha512-V4a05A3EgJcriqtDoJklDz1BOinWhC6P0hjUSxshA4KOZM7rGPCTto/usXs09zr1vvL28evl/NldSTv97j2bmg== dependencies: - "@safe-global/safe-apps-sdk" "8.0.0" + "@safe-global/safe-apps-sdk" "^8.1.0" events "^3.3.0" -"@safe-global/safe-apps-sdk@8.0.0", "@safe-global/safe-apps-sdk@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-sdk/-/safe-apps-sdk-8.0.0.tgz#9bdfe0e0d85e1b2d279bb840f40c4b930aaf8bc1" - integrity sha512-gYw0ki/EAuV1oSyMxpqandHjnthZjYYy+YWpTAzf8BqfXM3ItcZLpjxfg+3+mXW8HIO+3jw6T9iiqEXsqHaMMw== +"@safe-global/safe-apps-sdk@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-sdk/-/safe-apps-sdk-8.1.0.tgz#d1d0c69cd2bf4eef8a79c5d677d16971926aa64a" + integrity sha512-XJbEPuaVc7b9n23MqlF6c+ToYIS3f7P2Sel8f3cSBQ9WORE4xrSuvhMpK9fDSFqJ7by/brc+rmJR/5HViRr0/w== dependencies: "@safe-global/safe-gateway-typescript-sdk" "^3.5.3" viem "^1.0.0" @@ -4444,6 +4968,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@~1.1.2": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" + integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== + "@scure/bip32@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.0.tgz#6c8d980ef3f290987736acd0ee2e0f0d50068d87" @@ -4462,6 +4991,15 @@ "@noble/hashes" "~1.3.1" "@scure/base" "~1.1.0" +"@scure/bip32@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" + integrity sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA== + dependencies: + "@noble/curves" "~1.2.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.2" + "@scure/bip39@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b" @@ -6389,60 +6927,144 @@ "@babel/plugin-transform-react-jsx-source" "^7.19.6" react-refresh "^0.14.0" +"@vue/compiler-core@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.11.tgz#9fa26f8c81b9b34365f94ce1ed4d0e6e6f94a2ac" + integrity sha512-h97/TGWBilnLuRaj58sxNrsUU66fwdRKLOLQ9N/5iNDfp+DZhYH9Obhe0bXxhedl8fjAgpRANpiZfbgWyruQ0w== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/shared" "3.3.11" + estree-walker "^2.0.2" + source-map-js "^1.0.2" + +"@vue/compiler-dom@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.11.tgz#36a76ea3a296d41bad133a6912cb0a847d969e4f" + integrity sha512-zoAiUIqSKqAJ81WhfPXYmFGwDRuO+loqLxvXmfUdR5fOitPoUiIeFI9cTTyv9MU5O1+ZZglJVTusWzy+wfk5hw== + dependencies: + "@vue/compiler-core" "3.3.11" + "@vue/shared" "3.3.11" + +"@vue/compiler-sfc@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.11.tgz#acfae240c875d067e0e2c9a4e2d910074408c73b" + integrity sha512-U4iqPlHO0KQeK1mrsxCN0vZzw43/lL8POxgpzcJweopmqtoYy9nljJzWDIQS3EfjiYhfdtdk9Gtgz7MRXnz3GA== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/compiler-core" "3.3.11" + "@vue/compiler-dom" "3.3.11" + "@vue/compiler-ssr" "3.3.11" + "@vue/reactivity-transform" "3.3.11" + "@vue/shared" "3.3.11" + estree-walker "^2.0.2" + magic-string "^0.30.5" + postcss "^8.4.32" + source-map-js "^1.0.2" + +"@vue/compiler-ssr@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.11.tgz#598942a73b64f2bd3f95908b104a7fbb55fc41a2" + integrity sha512-Zd66ZwMvndxRTgVPdo+muV4Rv9n9DwQ4SSgWWKWkPFebHQfVYRrVjeygmmDmPewsHyznCNvJ2P2d6iOOhdv8Qg== + dependencies: + "@vue/compiler-dom" "3.3.11" + "@vue/shared" "3.3.11" + +"@vue/reactivity-transform@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.11.tgz#2bd486f4eff60c8724309925618891e722fcfadc" + integrity sha512-fPGjH0wqJo68A0wQ1k158utDq/cRyZNlFoxGwNScE28aUFOKFEnCBsvyD8jHn+0kd0UKVpuGuaZEQ6r9FJRqCg== + dependencies: + "@babel/parser" "^7.23.5" + "@vue/compiler-core" "3.3.11" + "@vue/shared" "3.3.11" + estree-walker "^2.0.2" + magic-string "^0.30.5" + +"@vue/reactivity@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.3.11.tgz#91f8e6c9ac60a595a5278c836b197628fd947a0d" + integrity sha512-D5tcw091f0nuu+hXq5XANofD0OXnBmaRqMYl5B3fCR+mX+cXJIGNw/VNawBqkjLNWETrFW0i+xH9NvDbTPVh7g== + dependencies: + "@vue/shared" "3.3.11" + +"@vue/runtime-core@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.3.11.tgz#63defba57bc54c1dac68a95b56c2633b1419193d" + integrity sha512-g9ztHGwEbS5RyWaOpXuyIVFTschclnwhqEbdy5AwGhYOgc7m/q3NFwr50MirZwTTzX55JY8pSkeib9BX04NIpw== + dependencies: + "@vue/reactivity" "3.3.11" + "@vue/shared" "3.3.11" + +"@vue/runtime-dom@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.3.11.tgz#1146d8d280b0fec4d2e18c4a4c8f8121d0cecc09" + integrity sha512-OlhtV1PVpbgk+I2zl+Y5rQtDNcCDs12rsRg71XwaA2/Rbllw6mBLMi57VOn8G0AjOJ4Mdb4k56V37+g8ukShpQ== + dependencies: + "@vue/runtime-core" "3.3.11" + "@vue/shared" "3.3.11" + csstype "^3.1.2" + +"@vue/server-renderer@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.3.11.tgz#409aed8031a125791e2143552975ecd1958ad601" + integrity sha512-AIWk0VwwxCAm4wqtJyxBylRTXSy1wCLOKbWxHaHiu14wjsNYtiRCSgVuqEPVuDpErOlRdNnuRgipQfXRLjLN5A== + dependencies: + "@vue/compiler-ssr" "3.3.11" + "@vue/shared" "3.3.11" + +"@vue/shared@3.3.11": + version "3.3.11" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.11.tgz#f6a038e15237edefcc90dbfe7edb806dd355c7bd" + integrity sha512-u2G8ZQ9IhMWTMXaWqZycnK4UthG1fA238CD+DP4Dm4WJi5hdUKKLg0RMRaRpDPNMdkTwIDkp7WtD0Rd9BH9fLw== + "@wagmi/chains@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-1.2.0.tgz#d59eaa70ec51a5fdcd113975926992acfb17ab12" integrity sha512-dmDRipsE54JfyudOBkuhEexqQWcrZqxn/qiujG8SBzMh/az/AH5xlJSA+j1CPWTx9+QofSMF3B7A4gb6XRmSaQ== -"@wagmi/chains@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-1.3.0.tgz#a17438d44abe98fdf1ec3a91df115c6bfaed39c0" - integrity sha512-7tyr1irTZQpA4/4HoIiJP3XYZuJIZuWiZ1V1j5WEG3cjm8TXIlMEzO0N+hT/cZKw4/UtF2EukvB8GkDWa2S77w== - -"@wagmi/connectors@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-2.6.2.tgz#91d48fbf9d06bc29f324e65681c1b4b989acbd5e" - integrity sha512-S6ieyh3jUmECOk70tl6ZDsknRF1zUB3jtj3LaOYc+GQmjynR24xQaG9HigjgLuHFINNsd6JEEWQ3pO+lD56c3Q== +"@wagmi/connectors@3.1.10": + version "3.1.10" + resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-3.1.10.tgz#830cd788579ef56a2526574914481f2d3aabc9f7" + integrity sha512-ZLJC1QaeiZarkF07Cr9mOlVjPO1Lf5TBx+JKBms2y5fUIXlKrxCfQgO/gDCureboI+Us2X3IRI659+XacSGpbA== dependencies: "@coinbase/wallet-sdk" "^3.6.6" - "@ledgerhq/connect-kit-loader" "^1.1.0" - "@safe-global/safe-apps-provider" "^0.17.1" - "@safe-global/safe-apps-sdk" "^8.0.0" - "@walletconnect/ethereum-provider" "2.8.4" + "@safe-global/safe-apps-provider" "^0.18.1" + "@safe-global/safe-apps-sdk" "^8.1.0" + "@walletconnect/ethereum-provider" "2.10.6" "@walletconnect/legacy-provider" "^2.0.0" - "@walletconnect/modal" "2.5.4" + "@walletconnect/modal" "2.6.2" + "@walletconnect/utils" "2.10.2" abitype "0.8.7" eventemitter3 "^4.0.7" -"@wagmi/core@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-1.3.3.tgz#5ba058d92bd843d209242532363af623acc9fb93" - integrity sha512-zjFWnPPcPKHY9t4j4aIyq4WlMTw/ucxYmmpwszK5ylHPnTbK1bWrGCtsrF0tIGWRTzEFVhJ/s3L4+qGLmoyu3A== +"@wagmi/core@1.4.12": + version "1.4.12" + resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-1.4.12.tgz#84558d1af746b2bcef02337dc3c46108242b1dd7" + integrity sha512-bLcYmmGgjtl3jAGo8X3Sm6oUwsdjbVxFMu9SWnwHdE4S9JdYeWM57dEhQgq8SYul2yQ7yY2/gimBf1Or0Ky3dQ== dependencies: - "@wagmi/chains" "1.3.0" - "@wagmi/connectors" "2.6.2" + "@wagmi/connectors" "3.1.10" abitype "0.8.7" eventemitter3 "^4.0.7" zustand "^4.3.1" -"@walletconnect/core@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.8.4.tgz#fc207c8fa35a53e30012b0c85b6ca933cec7d955" - integrity sha512-3CQHud4As0kPRvlW1w/wSWS2F3yXlAo5kSEJyRWLRPqXG+aSCVWM8cVM8ch5yoeyNIfOHhEINdsYMuJG1+yIJQ== +"@walletconnect/core@2.10.6": + version "2.10.6" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.10.6.tgz#786b0d2e2045c210c917e29bfa0498bbc210be20" + integrity sha512-Z4vh4ZdfcoQjgPEOxeuF9HUZCVLtV3MgRbS/awLIj/omDrFnOwlBhxi5Syr4Y8muVGC0ocRetQYHae0/gX5crQ== dependencies: "@walletconnect/heartbeat" "1.2.1" "@walletconnect/jsonrpc-provider" "1.0.13" "@walletconnect/jsonrpc-types" "1.0.3" "@walletconnect/jsonrpc-utils" "1.0.8" - "@walletconnect/jsonrpc-ws-connection" "^1.0.11" - "@walletconnect/keyvaluestorage" "^1.0.2" + "@walletconnect/jsonrpc-ws-connection" "1.0.14" + "@walletconnect/keyvaluestorage" "^1.1.1" "@walletconnect/logger" "^2.0.1" "@walletconnect/relay-api" "^1.0.9" "@walletconnect/relay-auth" "^1.0.4" "@walletconnect/safe-json" "^1.0.2" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.8.4" - "@walletconnect/utils" "2.8.4" + "@walletconnect/types" "2.10.6" + "@walletconnect/utils" "2.10.6" events "^3.3.0" lodash.isequal "4.5.0" uint8arrays "^3.1.0" @@ -6475,19 +7097,20 @@ dependencies: tslib "1.14.1" -"@walletconnect/ethereum-provider@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.8.4.tgz#c627c237b479194efc542b8475596bae12fde52d" - integrity sha512-z7Yz4w8t3eEFv8vQ8DLCgDWPah2aIIyC0iQdwhXgJenQTVuz7JJZRrJUUntzudipHK/owA394c1qTPF0rsMSeQ== +"@walletconnect/ethereum-provider@2.10.6": + version "2.10.6" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.10.6.tgz#53720771cc2d6accd452916a853ac927f26acbaa" + integrity sha512-bBQ+yUfxLv8VxNttgNKY7nED35gSVayO/BnLHbNKvyV1gpvSCla5mWB9MsXuQs70MK0g+/qtgRVSrOtdSubaNQ== dependencies: "@walletconnect/jsonrpc-http-connection" "^1.0.7" "@walletconnect/jsonrpc-provider" "^1.0.13" "@walletconnect/jsonrpc-types" "^1.0.3" "@walletconnect/jsonrpc-utils" "^1.0.8" - "@walletconnect/sign-client" "2.8.4" - "@walletconnect/types" "2.8.4" - "@walletconnect/universal-provider" "2.8.4" - "@walletconnect/utils" "2.8.4" + "@walletconnect/modal" "^2.4.3" + "@walletconnect/sign-client" "2.10.6" + "@walletconnect/types" "2.10.6" + "@walletconnect/universal-provider" "2.10.6" + "@walletconnect/utils" "2.10.6" events "^3.3.0" "@walletconnect/events@^1.0.1": @@ -6579,15 +7202,14 @@ "@walletconnect/jsonrpc-types" "^1.0.2" tslib "1.14.1" -"@walletconnect/jsonrpc-ws-connection@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.11.tgz#1ce59d86f273d576ca73385961303ebd44dd923f" - integrity sha512-TiFJ6saasKXD+PwGkm5ZGSw0837nc6EeFmurSPgIT/NofnOV4Tv7CVJqGQN0rQYoJUSYu21cwHNYaFkzNpUN+w== +"@walletconnect/jsonrpc-ws-connection@1.0.14": + version "1.0.14" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.14.tgz#eec700e74766c7887de2bd76c91a0206628732aa" + integrity sha512-Jsl6fC55AYcbkNVkwNM6Jo+ufsuCQRqViOQ8ZBPH9pRREHH9welbBiszuTLqEJiQcO/6XfFDl6bzCJIkrEi8XA== dependencies: "@walletconnect/jsonrpc-utils" "^1.0.6" "@walletconnect/safe-json" "^1.0.2" events "^3.3.0" - tslib "1.14.1" ws "^7.5.1" "@walletconnect/keyvaluestorage@^1.0.2": @@ -6598,6 +7220,15 @@ safe-json-utils "^1.1.1" tslib "1.14.1" +"@walletconnect/keyvaluestorage@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz#dd2caddabfbaf80f6b8993a0704d8b83115a1842" + integrity sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA== + dependencies: + "@walletconnect/safe-json" "^1.0.1" + idb-keyval "^6.2.1" + unstorage "^1.9.0" + "@walletconnect/legacy-client@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@walletconnect/legacy-client/-/legacy-client-2.0.0.tgz#9f2c09694789fd4b6c5d68d6423b44bac55aed30" @@ -6666,31 +7297,30 @@ pino "7.11.0" tslib "1.14.1" -"@walletconnect/modal-core@2.5.4": - version "2.5.4" - resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.5.4.tgz#7d739a90a9cf103067eea46507ea649e8dada436" - integrity sha512-ISe4LqmEDFU7b6rLgonqaEtMXzG6ko13HA7S8Ty3d7GgfAEe29LM1dq3zo8ehEOghhofhj1PiiNfvaogZKzT1g== +"@walletconnect/modal-core@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.6.2.tgz#d73e45d96668764e0c8668ea07a45bb8b81119e9" + integrity sha512-cv8ibvdOJQv2B+nyxP9IIFdxvQznMz8OOr/oR/AaUZym4hjXNL/l1a2UlSQBXrVjo3xxbouMxLb3kBsHoYP2CA== dependencies: - buffer "6.0.3" - valtio "1.10.6" + valtio "1.11.2" -"@walletconnect/modal-ui@2.5.4": - version "2.5.4" - resolved "https://registry.yarnpkg.com/@walletconnect/modal-ui/-/modal-ui-2.5.4.tgz#0433fb0226dd47e17fede620c5a5ff332baed155" - integrity sha512-5qLLjwbE3YC4AsCVhf8J87otklkApcQ5DCMykOcS0APPv8lKQ46JxpQhfWwRYaUkuIiHonI9h1YxFARDkoaI9g== +"@walletconnect/modal-ui@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@walletconnect/modal-ui/-/modal-ui-2.6.2.tgz#fa57c087c57b7f76aaae93deab0f84bb68b59cf9" + integrity sha512-rbdstM1HPGvr7jprQkyPggX7rP4XiCG85ZA+zWBEX0dVQg8PpAgRUqpeub4xQKDgY7pY/xLRXSiCVdWGqvG2HA== dependencies: - "@walletconnect/modal-core" "2.5.4" - lit "2.7.5" + "@walletconnect/modal-core" "2.6.2" + lit "2.8.0" motion "10.16.2" qrcode "1.5.3" -"@walletconnect/modal@2.5.4": - version "2.5.4" - resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.5.4.tgz#66659051f9c0f35c151d3a8d940f8c64d42fab74" - integrity sha512-JAKMcCd4JQvSEr7pNitg3OBke4DN1JyaQ7bdi3x4T7oLgOr9Y88qdkeOXko/0aJonDHJsM88hZ10POQWmKfEMA== +"@walletconnect/modal@2.6.2", "@walletconnect/modal@^2.4.3": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.6.2.tgz#4b534a836f5039eeb3268b80be7217a94dd12651" + integrity sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA== dependencies: - "@walletconnect/modal-core" "2.5.4" - "@walletconnect/modal-ui" "2.5.4" + "@walletconnect/modal-core" "2.6.2" + "@walletconnect/modal-ui" "2.6.2" "@walletconnect/randombytes@^1.0.3": version "1.0.3" @@ -6736,19 +7366,19 @@ dependencies: tslib "1.14.1" -"@walletconnect/sign-client@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.8.4.tgz#35e7cfe9442c65d7f667a7c20f1a5ee7e2a6e576" - integrity sha512-eRvWtKBAgzo/rbIkw+rkKco2ulSW8Wor/58UsOBsl9DKr1rIazZd4ZcUdaTjg9q8AT1476IQakCAIuv+1FvJwQ== +"@walletconnect/sign-client@2.10.6": + version "2.10.6" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.10.6.tgz#722d2c2844565e2826dce6a6d3a36c9b3ca1ea91" + integrity sha512-EvUWjaZBQu2yKnH5/5F2qzbuiIuUN9ZgrNKgvXkw5z1Dq5RJCks0S9/MFlKH/ZSGqXnLl7uAzBXtoX4sMgbCMA== dependencies: - "@walletconnect/core" "2.8.4" + "@walletconnect/core" "2.10.6" "@walletconnect/events" "^1.0.1" "@walletconnect/heartbeat" "1.2.1" "@walletconnect/jsonrpc-utils" "1.0.8" "@walletconnect/logger" "^2.0.1" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.8.4" - "@walletconnect/utils" "2.8.4" + "@walletconnect/types" "2.10.6" + "@walletconnect/utils" "2.10.6" events "^3.3.0" "@walletconnect/time@^1.0.2": @@ -6758,10 +7388,10 @@ dependencies: tslib "1.14.1" -"@walletconnect/types@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.8.4.tgz#23fad8593b094c7564d72f179e33b1cac9324a88" - integrity sha512-Fgqe87R7rjMOGSvx28YPLTtXM6jj+oUOorx8cE+jEw2PfpWp5myF21aCdaMBR39h0QHij5H1Z0/W9e7gm4oC1Q== +"@walletconnect/types@2.10.2": + version "2.10.2" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.10.2.tgz#68e433a29ec2cf42d79d8b50c77bd5c1d91db721" + integrity sha512-luNV+07Wdla4STi9AejseCQY31tzWKQ5a7C3zZZaRK/di+rFaAAb7YW04OP4klE7tw/mJRGPTlekZElmHxO8kQ== dependencies: "@walletconnect/events" "^1.0.1" "@walletconnect/heartbeat" "1.2.1" @@ -6770,25 +7400,37 @@ "@walletconnect/logger" "^2.0.1" events "^3.3.0" -"@walletconnect/universal-provider@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.8.4.tgz#7b62a76a7d99ea41c67374da54aaa4f1b4bc1d03" - integrity sha512-JRpOXKIciRMzd03zZxM1WDsYHo/ZS86zZrZ1aCHW1d45ZLP7SbGPRHzZgBY3xrST26yTvWIlRfTUEYn50fzB1g== +"@walletconnect/types@2.10.6": + version "2.10.6" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.10.6.tgz#d9920ed4fd0113e0addbda8e7e73a5176a3163fd" + integrity sha512-WgHfiTG1yakmxheaBRiXhUdEmgxwrvsAdOIWaMf/spvrzVKYh6sHI3oyEEky5qj5jjiMiyQBeB57QamzCotbcQ== + dependencies: + "@walletconnect/events" "^1.0.1" + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-types" "1.0.3" + "@walletconnect/keyvaluestorage" "^1.1.1" + "@walletconnect/logger" "^2.0.1" + events "^3.3.0" + +"@walletconnect/universal-provider@2.10.6": + version "2.10.6" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.10.6.tgz#1a6c42517581f11ce275474bc70d0eb4f1044525" + integrity sha512-CEivusqqoD31BhCTKp08DnrccfGjwD9MFjZs5BNRorDteRFE8zVm9LmP6DSiNJCw82ZajGlZThggLQ/BAATfwA== dependencies: "@walletconnect/jsonrpc-http-connection" "^1.0.7" "@walletconnect/jsonrpc-provider" "1.0.13" "@walletconnect/jsonrpc-types" "^1.0.2" "@walletconnect/jsonrpc-utils" "^1.0.7" "@walletconnect/logger" "^2.0.1" - "@walletconnect/sign-client" "2.8.4" - "@walletconnect/types" "2.8.4" - "@walletconnect/utils" "2.8.4" + "@walletconnect/sign-client" "2.10.6" + "@walletconnect/types" "2.10.6" + "@walletconnect/utils" "2.10.6" events "^3.3.0" -"@walletconnect/utils@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.8.4.tgz#8dbd3beaef39388be2398145a5f9a061a0317518" - integrity sha512-NGw6BINYNeT9JrQrnxldAPheO2ymRrwGrgfExZMyrkb1MShnIX4nzo4KirKInM4LtrY6AA/v0Lu3ooUdfO+xIg== +"@walletconnect/utils@2.10.2": + version "2.10.2" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.10.2.tgz#1f2c6a2f1bb95bcc4517b1e94aa7164c9286eb46" + integrity sha512-syxXRpc2yhSknMu3IfiBGobxOY7fLfLTJuw+ppKaeO6WUdZpIit3wfuGOcc0Ms3ZPFCrGfyGOoZsCvgdXtptRg== dependencies: "@stablelib/chacha20poly1305" "1.0.1" "@stablelib/hkdf" "1.0.1" @@ -6798,7 +7440,27 @@ "@walletconnect/relay-api" "^1.0.9" "@walletconnect/safe-json" "^1.0.2" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.8.4" + "@walletconnect/types" "2.10.2" + "@walletconnect/window-getters" "^1.0.1" + "@walletconnect/window-metadata" "^1.0.1" + detect-browser "5.3.0" + query-string "7.1.3" + uint8arrays "^3.1.0" + +"@walletconnect/utils@2.10.6": + version "2.10.6" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.10.6.tgz#749b37d14e291e346862e7027ec7548463350226" + integrity sha512-oRsWWhN2+hi3aiDXrQEOfysz6FHQJGXLsNQPVt+WIBJplO6Szmdau9dbleD88u1iiT4GKPqE0R9FOYvvPm1H/w== + dependencies: + "@stablelib/chacha20poly1305" "1.0.1" + "@stablelib/hkdf" "1.0.1" + "@stablelib/random" "^1.0.2" + "@stablelib/sha256" "1.0.1" + "@stablelib/x25519" "^1.0.3" + "@walletconnect/relay-api" "^1.0.9" + "@walletconnect/safe-json" "^1.0.2" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.10.6" "@walletconnect/window-getters" "^1.0.1" "@walletconnect/window-metadata" "^1.0.1" detect-browser "5.3.0" @@ -6820,37 +7482,103 @@ "@walletconnect/window-getters" "^1.0.1" tslib "1.14.1" -"@web3modal/core@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@web3modal/core/-/core-2.6.2.tgz#776f18b3014eab1d99c257cba7acd426d786d981" - integrity sha512-7gi618Z+bFOwEzhdxROt4AC8N45oiPHJFEA0AX3PxhB+ni5Usc1VQ03jQhk7DnQLDnQq+3hCY71F1escdxkdBA== +"@web3modal/common@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/common/-/common-3.5.0.tgz#010f24c7170c70fbad15fae9500ede8a18b43e0d" + integrity sha512-cpNFf6TiPYXEozYtIzLH9PcWSlFeoeDM6hTiXEVNutJeFRppFICLOAsyFoa7MszexFJaZ21cAxj/PzI2GIKGjA== + dependencies: + dayjs "1.11.10" + +"@web3modal/core@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/core/-/core-3.5.0.tgz#089c47c8dc9a41f1f3c50374d682860b7c34e81c" + integrity sha512-yZFk4YZDcmOhRyKkQYP0xdxK3JfdVwayive6UyBRY1bOxl4V4PAs+vRNLNY/3LulMMpqaHZGuD1zty1GXJ7INA== + dependencies: + "@web3modal/common" "3.5.0" + valtio "1.12.1" + +"@web3modal/polyfills@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/polyfills/-/polyfills-3.5.0.tgz#e3ee329a14de924ba796dccaa58dfafe42ac5819" + integrity sha512-QxVgUd+GKENGLo84bNTXjHk78l61Yr65LLvq5GPQWwrIPWFDqP2Nf7ixtaI36tTx29VN2SVyzBXGMhtrL73ZTg== dependencies: buffer "6.0.3" - valtio "1.10.6" -"@web3modal/ethereum@^2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@web3modal/ethereum/-/ethereum-2.6.2.tgz#e6522ddf00444cb3e6e54d390a74864c329cee5e" - integrity sha512-8QCzJj0+x6y/7V++DQnXdPjdmMvq+zm/Fl8CEKhNlni9p4H406q7ramjdJEu1Bk4xtCklYh/aU3ZGR5wUIDysA== +"@web3modal/scaffold-react@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold-react/-/scaffold-react-3.5.0.tgz#3054b1a9d95a69dbf32d91c539b9bb475bf7e8db" + integrity sha512-4F4QkUMa3VFcmbP2gYOQ+h4x23EFa13BcWEZepQ47q9HbW/sL3zLmS7k44EpyhLMSEX4HrPHXiUlGTr9rK038g== + dependencies: + "@web3modal/scaffold" "3.5.0" -"@web3modal/react@^2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@web3modal/react/-/react-2.6.2.tgz#1c65cd3a2a51b767c1287cb04dc20fc0c94ee998" - integrity sha512-Fi4rFCFI5L0w2Mx8hmY6Nwh4fQapGfKZA2JgXT8cduTk9N+Qov+clwyt9bhRC/60F1g8Kq+/L8a3T1OoEFvHyg== +"@web3modal/scaffold-utils@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold-utils/-/scaffold-utils-3.5.0.tgz#c05d136538277899d4c6deb76993a5e4ade94767" + integrity sha512-RpNp5N+c8oaPyy48u9pOj65uuZDmMAluj2ZUWRoh3FqoN+gshpQsCbbPwtuTug4MsGc84i8RJorTEargzrKthg== + dependencies: + "@web3modal/polyfills" "3.5.0" + "@web3modal/scaffold" "3.5.0" + +"@web3modal/scaffold-vue@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold-vue/-/scaffold-vue-3.5.0.tgz#04222fd36a4421a860c9662f73accb3bd51b6f94" + integrity sha512-babm6SpuOeYxyTfdg9v603lYCrOFbFg1Y8KHTCAISu3Tk4GEnJ99wQVUE1ujX2CHoLU8GvAeyVKszJniKyPaJQ== + dependencies: + "@web3modal/scaffold" "3.5.0" + +"@web3modal/scaffold@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold/-/scaffold-3.5.0.tgz#808def7fe15c729e85a7f7ff59689eebd4ba5b52" + integrity sha512-wBiOeFdpANOiXqf2o0bi1cd+P427oBf5p3hb5nRJ4abBOPtjr6g0rEQgdLwtalBRt4K4qLAElOwem6toRVViig== + dependencies: + "@web3modal/core" "3.5.0" + "@web3modal/ui" "3.5.0" + lit "3.1.0" + +"@web3modal/siwe@*": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/siwe/-/siwe-3.5.0.tgz#5f7fa6a7209c6ddddcd008ad47bb4dc3815d4a37" + integrity sha512-i9AvSH3uA93lu7z79d90aMaHx3Kn/EhblRJa2NOykADuAZX3UeXkX9KX7pkLVEYD1TkQN4mOHTFRQ6LeXq9EcA== dependencies: - "@web3modal/core" "2.6.2" - "@web3modal/ui" "2.6.2" + "@web3modal/core" "3.5.0" + "@web3modal/scaffold-utils" "3.5.0" + optionalDependencies: + react ">=17" + react-dom ">=17" + vue ">=3" -"@web3modal/ui@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@web3modal/ui/-/ui-2.6.2.tgz#ed88f06e9c83bd0b3bb22437a5a8ffc460ef21db" - integrity sha512-bhBNsookaqkTdLjyERBA7XM9KV8XPQ2bWAGd3roIgAFxLlaoWqrV0pPZHLtp+C97AglV4mbeqvFsHZ0jwpm8lw== +"@web3modal/ui@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/ui/-/ui-3.5.0.tgz#e925cf829d5b0904c5693094db1c3a95c4ee5c1a" + integrity sha512-1H8pHCxcMDPxx1jKYMycSqilVGKDnKEGqVUa+NBhu9YeebIqB9WB5wWhyROLADsX/XzKwXUqhQndHDpcLyMyFw== dependencies: - "@web3modal/core" "2.6.2" - lit "2.7.5" - motion "10.16.2" + lit "3.1.0" qrcode "1.5.3" +"@web3modal/wagmi@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/wagmi/-/wagmi-3.5.0.tgz#c0f6e92ff4c5e16febac74eddc180333bb039a98" + integrity sha512-bQoytNkcVbjWfZsePKAv+XF9KDtDx1iqitoC4RPold9oB5RukEgGNHFQ7AcDKTwjkmiE3JqwyubQyvupZ2Z/Yg== + dependencies: + "@web3modal/polyfills" "3.5.0" + "@web3modal/scaffold" "3.5.0" + "@web3modal/scaffold-react" "3.5.0" + "@web3modal/scaffold-utils" "3.5.0" + "@web3modal/scaffold-vue" "3.5.0" + "@web3modal/wallet" "3.5.0" + optionalDependencies: + "@web3modal/siwe" "*" + react ">=17" + react-dom ">=17" + vue ">=3" + +"@web3modal/wallet@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@web3modal/wallet/-/wallet-3.5.0.tgz#1123e84d6dad20957de2223eeab5bb70a9d27fc4" + integrity sha512-uNAOZS94Dfzp1ha3X1MztFS4R6XuOZ/Nfe0oiSvLAbeXb2pbV/JDJR1g0LjYiNU/XsDWYEW9WYeRUyg9ZLdUdg== + dependencies: + zod "3.22.4" + "@xstate/fsm@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.4.0.tgz#6fd082336fde4d026e9e448576189ee5265fa51a" @@ -6894,6 +7622,11 @@ abitype@0.8.7: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.8.7.tgz#e4b3f051febd08111f486c0cc6a98fa72d033622" integrity sha512-wQ7hV8Yg/yKmGyFpqrNZufCxbszDe5es4AZGYPBitocfSqXtjrTG9JMWFcc4N30ukl2ve48aBTwt7NJxVQdU3w== +abitype@0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" + integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -6929,7 +7662,7 @@ acorn-walk@^8.0.2, acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.0.4, acorn@^8.8.2: +acorn@^8.0.4, acorn@^8.10.0, acorn@^8.8.2: version "8.11.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== @@ -6944,6 +7677,11 @@ acorn@^8.8.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + aes-js@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" @@ -7029,6 +7767,11 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.1.tgz#63cd61c72283a71cb30bd881dbb60adada74bc70" integrity sha512-qDOv24WjnYuL+wbwHdlsYZFy+cgPtrYw0Tn7GLORicQp9BkQLzrgI3Pm4VyR9ERZ41YTn7KlMPuL1n05WdZvmg== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -7042,7 +7785,7 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -anymatch@~3.1.2: +anymatch@^3.1.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -7050,6 +7793,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -7207,9 +7955,9 @@ axe-core@^4.4.3: integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w== axios@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" - integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -7330,6 +8078,11 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + bigint-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" @@ -7378,7 +8131,12 @@ blo@^1.1.1: resolved "https://registry.yarnpkg.com/blo/-/blo-1.1.1.tgz#ed781c5c516fba484ec8ec86105dc27f6c553209" integrity sha512-1uGZInlRD4X1WQP2G1QjDGwGZ8HdGgFKqnzyRdA2TYYc0MOQCmCi37RTQ8oJuI0UF6DYFKXHwV/t1kZkO/fTaA== -bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0: +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -7419,6 +8177,11 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + browserslist@^4.21.3, browserslist@^4.21.4: version "4.21.4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" @@ -7543,7 +8306,7 @@ chakra-react-select@^4.4.3: dependencies: react-select "5.7.0" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -7610,6 +8373,13 @@ ci-info@^3.7.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== +citty@^0.1.4, citty@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.5.tgz#fe37ceae5dc764af75eb2fece99d2bf527ea4e50" + integrity sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ== + dependencies: + consola "^3.2.3" + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -7658,6 +8428,15 @@ client-only@0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +clipboardy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092" + integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg== + dependencies: + arch "^2.2.0" + execa "^5.1.1" + is-wsl "^2.2.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -7681,6 +8460,16 @@ clsx@^1.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -7784,6 +8573,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +consola@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f" + integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" @@ -7794,6 +8588,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-es@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.0.0.tgz#4759684af168dfc54365b2c2dda0a8d7ee1e4865" + integrity sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ== + cookie@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" @@ -7863,7 +8662,7 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "2.6.7" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -7872,10 +8671,10 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-js@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" - integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== css-box-model@1.2.1: version "1.2.1" @@ -7909,6 +8708,17 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + css-tree@^1.1.2, css-tree@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" @@ -7917,7 +8727,23 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" -css-what@^6.0.1: +css-tree@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -7937,6 +8763,13 @@ cssfilter@0.0.10: resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== +csso@5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" @@ -7971,6 +8804,11 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14" @@ -8220,6 +9058,15 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== +dappscout-iframe@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.1.0.tgz#c971d8a8aa4fb3d64ca357700d3ac809dcdecb0d" + integrity sha512-nL65Uuv2+r2ujELyBtw+VL6PYkUtleCvKi2XaeMiroZz6tieeUw338PEOc6cZaIfwXk59rFwNR0FZki2HnOrIA== + dependencies: + ethers "^5.7.2" + react "^18.2.0" + react-dom "^18.2.0" + data-uri-to-buffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" @@ -8239,12 +9086,17 @@ dateformat@^4.6.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== +dayjs@1.11.10: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + dayjs@^1.11.5: version "1.11.5" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -8346,6 +9198,11 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +defu@^6.1.2, defu@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.3.tgz#6d7f56bc61668e844f9f593ace66fd67ef1205fd" + integrity sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ== + delaunator@5: version "5.0.0" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" @@ -8363,16 +9220,36 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + depd@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +derive-valtio@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/derive-valtio/-/derive-valtio-0.1.0.tgz#4b9fb393dfefccfef15fcbbddd745dd22d5d63d7" + integrity sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A== + +destr@^2.0.1, destr@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.2.tgz#8d3c0ee4ec0a76df54bc8b819bca215592a8c218" + integrity sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg== + detect-browser@5.3.0, detect-browser@^5.2.0, detect-browser@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detect-libc@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" @@ -8446,12 +9323,21 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + dom-to-image@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/dom-to-image/-/dom-to-image-2.6.0.tgz#8a503608088c87b1c22f9034ae032e1898955867" integrity sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA== -domelementtype@^2.0.1, domelementtype@^2.2.0: +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -8470,6 +9356,13 @@ domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + dompurify@=3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.6.tgz#925ebd576d54a9531b5d76f0a5bef32548351dae" @@ -8484,6 +9377,15 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dotenv-cli@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-6.0.0.tgz#8a30cbc59d0a8aaa166b2fee0a9a55e23a1223ab" @@ -8534,6 +9436,19 @@ electron-to-chromium@^1.4.251: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.276.tgz#17837b19dafcc43aba885c4689358b298c19b520" integrity sha512-EpuHPqu8YhonqLBXHoU6hDJCD98FCe6KDoet3/gY1qsQ6usjJoHqBH2YIVs8FXaAtHwVL8Uqa/fsYao/vq9VWQ== +elliptic@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -8574,6 +9489,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + entities@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" @@ -9143,6 +10063,42 @@ ethereum-cryptography@^2.0.0: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +ethers@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -9158,7 +10114,7 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^5.0.0: +execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -9407,6 +10363,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -9562,6 +10526,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port-please@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.1.tgz#2556623cddb4801d823c0a6a15eec038abb483be" + integrity sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA== + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -9633,6 +10602,17 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.3.10: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -9763,10 +10743,10 @@ graphql-ws@^5.11.3: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.11.3.tgz#eaf8e6baf669d167975cff13ad86abca4ecfe82f" integrity sha512-fU8zwSgAX2noXAsuFiCZ8BtXeXZOzXyK5u1LloCdacsVth4skdBMPO74EG51lBoWSIZ8beUocdpV8+cQHBODnQ== -graphql@^16.6.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" - integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +graphql@^16.8.1: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== gzip-size@^6.0.0: version "6.0.0" @@ -9775,6 +10755,20 @@ gzip-size@^6.0.0: dependencies: duplexer "^0.1.2" +h3@^1.8.1, h3@^1.8.2: + version "1.9.0" + resolved "https://registry.yarnpkg.com/h3/-/h3-1.9.0.tgz#c5f512a93026df9837db6f30c9ef51135dd46752" + integrity sha512-+F3ZqrNV/CFXXfZ2lXBINHi+rM4Xw3CDC5z2CDK3NMPocjonKipGLLDSkrqY9DOrioZNPTIdDMWfQKm//3X2DA== + dependencies: + cookie-es "^1.0.0" + defu "^6.1.3" + destr "^2.0.2" + iron-webcrypto "^1.0.0" + radix3 "^1.1.0" + ufo "^1.3.2" + uncrypto "^0.1.3" + unenv "^1.7.4" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -9821,7 +10815,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash.js@^1.1.7: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -9852,6 +10846,11 @@ hastscript@^6.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + help-me@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.1.0.tgz#c105e78ba490d6fcaa61a3d0cd06e0054554efab" @@ -9880,6 +10879,15 @@ highlight.js@^10.4.1, highlight.js@~10.7.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -9918,6 +10926,11 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-shutdown@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/http-shutdown/-/http-shutdown-1.2.2.tgz#41bc78fc767637c4c95179bc492f312c0ae64c5f" + integrity sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw== + https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -9960,6 +10973,11 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== +idb-keyval@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" + integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -10059,6 +11077,26 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + +iron-webcrypto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz#e3b689c0c61b434a0a4cb82d0aeabbc8b672a867" + integrity sha512-anOK1Mktt8U1Xi7fCM3RELTuYbnFikQY5VtrDj7kPgpejV7d43tWKhzgioO0zpkazLEL/j/iayRqnJhrGfqUsg== + is-alphabetical@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" @@ -10364,6 +11402,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isows@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" + integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -10406,6 +11449,15 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jaeger-client@^3.15.0: version "3.19.0" resolved "https://registry.yarnpkg.com/jaeger-client/-/jaeger-client-3.19.0.tgz#9b5bd818ebd24e818616ee0f5cffe1722a53ae6e" @@ -10819,6 +11871,11 @@ jest@^29.2.1: import-local "^3.0.2" jest-cli "^29.3.1" +jiti@^1.20.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + joi@^17.3.0: version "17.11.0" resolved "https://registry.yarnpkg.com/joi/-/joi-17.11.0.tgz#aa9da753578ec7720e6f0ca2c7046996ed04fc1a" @@ -10850,6 +11907,11 @@ js-sdsl@^4.1.4: resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0" integrity sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ== +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -10989,6 +12051,11 @@ json5@^2.2.2: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -11111,6 +12178,29 @@ lint-staged@>=10: string-argv "^0.3.1" yaml "^2.1.1" +listhen@^1.5.5: + version "1.5.5" + resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.5.5.tgz#58915512af70f770aa3e9fb19367adf479bb58c4" + integrity sha512-LXe8Xlyh3gnxdv4tSjTjscD1vpr/2PRpzq8YIaMJgyKzRG8wdISlWVWnGThJfHnlJ6hmLt2wq1yeeix0TEbuoA== + dependencies: + "@parcel/watcher" "^2.3.0" + "@parcel/watcher-wasm" "2.3.0" + citty "^0.1.4" + clipboardy "^3.0.0" + consola "^3.2.3" + defu "^6.1.2" + get-port-please "^3.1.1" + h3 "^1.8.1" + http-shutdown "^1.2.2" + jiti "^1.20.0" + mlly "^1.4.2" + node-forge "^1.3.1" + pathe "^1.1.1" + std-env "^3.4.3" + ufo "^1.3.0" + untun "^0.1.2" + uqr "^0.1.2" + listr2@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" @@ -11134,6 +12224,15 @@ lit-element@^3.3.0: "@lit/reactive-element" "^1.3.0" lit-html "^2.7.0" +lit-element@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-4.0.2.tgz#1a519896d5ab7c7be7a8729f400499e38779c093" + integrity sha512-/W6WQZUa5VEXwC7H9tbtDMdSs9aWil3Ou8hU6z2cOKWbsm/tXPAcsoaHVEtrDo0zcOIE5GF6QgU55tlGL2Nihg== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.1.2" + "@lit/reactive-element" "^2.0.0" + lit-html "^3.1.0" + lit-html@^2.7.0: version "2.7.4" resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.7.4.tgz#6d75001977c206683685b9d76594a516afda2954" @@ -11141,14 +12240,37 @@ lit-html@^2.7.0: dependencies: "@types/trusted-types" "^2.0.2" -lit@2.7.5: - version "2.7.5" - resolved "https://registry.yarnpkg.com/lit/-/lit-2.7.5.tgz#60bc82990cfad169d42cd786999356dcf79b035f" - integrity sha512-i/cH7Ye6nBDUASMnfwcictBnsTN91+aBjXoTHF2xARghXScKxpD4F4WYI+VLXg9lqbMinDfvoI7VnZXjyHgdfQ== +lit-html@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.8.0.tgz#96456a4bb4ee717b9a7d2f94562a16509d39bffa" + integrity sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q== + dependencies: + "@types/trusted-types" "^2.0.2" + +lit-html@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-3.1.0.tgz#a7b93dd682073f2e2029656f4e9cd91e8034c196" + integrity sha512-FwAjq3iNsaO6SOZXEIpeROlJLUlrbyMkn4iuv4f4u1H40Jw8wkeR/OUXZUHUoiYabGk8Y4Y0F/rgq+R4MrOLmA== + dependencies: + "@types/trusted-types" "^2.0.2" + +lit@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/lit/-/lit-2.8.0.tgz#4d838ae03059bf9cafa06e5c61d8acc0081e974e" + integrity sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA== dependencies: "@lit/reactive-element" "^1.6.0" lit-element "^3.3.0" - lit-html "^2.7.0" + lit-html "^2.8.0" + +lit@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lit/-/lit-3.1.0.tgz#76429b85dc1f5169fed499a0f7e89e2e619010c9" + integrity sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w== + dependencies: + "@lit/reactive-element" "^2.0.0" + lit-element "^4.0.0" + lit-html "^3.1.0" locate-path@^5.0.0: version "5.0.0" @@ -11174,6 +12296,16 @@ lodash.debounce@^4, lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + lodash.isequal@4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" @@ -11239,6 +12371,11 @@ lowlight@^1.17.0: fault "^1.0.0" highlight.js "~10.7.0" +lru-cache@^10.0.2, "lru-cache@^9.1.1 || ^10.0.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" + integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -11258,6 +12395,13 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +magic-string@^0.30.5: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -11293,6 +12437,16 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -11348,6 +12502,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -11370,11 +12529,16 @@ minim@~0.23.8: dependencies: lodash "^4.15.0" -minimalistic-assert@^1.0.1: +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -11396,6 +12560,13 @@ minimatch@^7.4.3: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.1.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -11406,6 +12577,11 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + mixpanel-browser@^2.47.0: version "2.47.0" resolved "https://registry.yarnpkg.com/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz#4e7fd3bb660c6f63443efbd169d1cd327db71ed4" @@ -11416,6 +12592,16 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mlly@^1.2.0, mlly@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" + integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== + dependencies: + acorn "^8.10.0" + pathe "^1.1.1" + pkg-types "^1.0.3" + ufo "^1.3.0" + mockdate@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" @@ -11443,6 +12629,11 @@ motion@10.16.2: "@motionone/utils" "^10.15.1" "@motionone/vue" "^10.16.2" +mri@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + mrmime@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" @@ -11482,21 +12673,26 @@ nan@^2.14.0, nan@^2.14.1, nan@^2.17.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== +napi-wasm@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" + integrity sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -11554,6 +12750,11 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-addon-api@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.0.0.tgz#8136add2f510997b3b94814f4af1cce0b0e3962e" + integrity sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA== + node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" @@ -11567,6 +12768,11 @@ node-fetch-commonjs@^3.3.1: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" +node-fetch-native@^1.4.0, node-fetch-native@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.4.1.tgz#5a336e55b4e1b1e72b9927da09fecd2b374c9be5" + integrity sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w== + node-fetch@2, node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -11597,11 +12803,24 @@ node-fetch@^3.2.9: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: version "4.6.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== +node-html-parser@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.11.tgz#387378111348c001a5c5fbebb7f478fdf65610aa" + integrity sha512-FAgwwZ6h0DSDWxfD0Iq1tsDcBCxdJB1nXpLPPxX8YyVWzbfCjKWEzaynF4gZZ/8hziUmp7ZSaKylcn0iKhufUQ== + dependencies: + css-select "^5.1.0" + he "1.2.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -11761,6 +12980,15 @@ obuf@~1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +ofetch@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.3.tgz#588cb806a28e5c66c2c47dd8994f9059a036d8c0" + integrity sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg== + dependencies: + destr "^2.0.1" + node-fetch-native "^1.4.0" + ufo "^1.3.0" + on-exit-leak-free@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" @@ -11975,6 +13203,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" @@ -11985,6 +13221,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.0, pathe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" + integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== + pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" @@ -12157,6 +13398,15 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + playwright-core@1.35.1: version "1.35.1" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d" @@ -12227,21 +13477,12 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.19: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.4.23: - version "8.4.24" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" - integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== +postcss@^8.4.19, postcss@^8.4.23, postcss@^8.4.32: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== dependencies: - nanoid "^3.3.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -12539,6 +13780,11 @@ quick-format-unescaped@^4.0.3: resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== +radix3@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.0.tgz#9745df67a49c522e94a33d0a93cf743f104b6e0d" + integrity sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A== + ramda-adjunct@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/ramda-adjunct/-/ramda-adjunct-4.0.0.tgz#99873cc707e86207ec7e757385144b3f235b7c59" @@ -12617,7 +13863,7 @@ react-device-detect@^2.2.3: dependencies: ua-parser-js "^1.0.33" -react-dom@18.2.0: +react-dom@18.2.0, react-dom@>=17, react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -12806,7 +14052,7 @@ react@17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -react@18.2.0: +react@18.2.0, react@>=17, react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -12882,6 +14128,18 @@ recrawl-sync@^2.0.3: sucrase "^3.20.3" tslib "^1.9.3" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + redux-immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-4.0.0.tgz#3a1a32df66366462b63691f0e1dc35e472bbc9f3" @@ -13235,6 +14493,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +scrypt-js@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + scslre@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/scslre/-/scslre-0.1.6.tgz#71a2832e4bf3a9254973a04fbed90aec94f75757" @@ -13325,6 +14588,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -13408,7 +14676,7 @@ sonic-boom@^3.0.0, sonic-boom@^3.1.0: dependencies: atomic-sleep "^1.0.0" -source-map-js@^1.0.2: +source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -13468,11 +14736,21 @@ stampit@^4.3.2: resolved "https://registry.yarnpkg.com/stampit/-/stampit-4.3.2.tgz#cfd3f607dd628a161ce6305621597994b4d56573" integrity sha512-pE2org1+ZWQBnIxRPrBM2gVupkuDD0TTNIo1H6GdT/vO82NXli2z8lRE8cu/nBIHrcOCXFBAHpb9ZldrB2/qOA== +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + state-local@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== +std-env@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.6.0.tgz#94807562bddc68fa90f2e02c5fd5b6865bb4e98e" + integrity sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg== + stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -13519,7 +14797,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw== -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13528,7 +14806,7 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.0: +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -13615,7 +14893,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13732,6 +15010,20 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svg-icons-cli@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/svg-icons-cli/-/svg-icons-cli-0.0.5.tgz#501c43f80fe23784e826b47c4fe2e71e7ea5fa67" + integrity sha512-6U7RB1n8qroIuJYjMO/HVk/3OXZS0m48Qu+pFXNGp2oRwhTdkTjMJ2PThbzzAwLNzBNmQ5qMfzQq5vqvp4Ocdg== + dependencies: + "@clack/prompts" "^0.7.0" + clsx "^2.0.0" + glob "^10.3.10" + node-html-parser "^6.1.11" + svgo "^3.0.4" + tailwind-merge "^2.0.0" + tiny-parse-argv "^2.2.0" + typescript "^5.2.2" + svg-parser@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" @@ -13750,6 +15042,19 @@ svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" +svgo@^3.0.4: + version "3.1.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.1.0.tgz#7e63855c8da73297d5d5765e968f9679a0f8d24a" + integrity sha512-R5SnNA89w1dYgNv570591F66v34b3eQShpIBcQtZtM5trJwm1VvxbIoMpRYY3ybTAutcKTLEmTsdnaknOHbiQA== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.2.1" + css-what "^6.1.0" + csso "5.0.5" + picocolors "^1.0.0" + swagger-client@^3.22.3: version "3.23.1" resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.23.1.tgz#22364b76b6f61b7c69f8846563fad59f28891380" @@ -13829,6 +15134,13 @@ tabbable@^4.0.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ== +tailwind-merge@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.1.0.tgz#541b407e0ec255651e92571d96b685e48f01999c" + integrity sha512-l11VvI4nSwW7MtLSLYT4ldidDEUwQAMWuSHk7l4zcXZDgnCRa0V3OdCwFfM7DCzakVXMNRwAeje9maFFXT71dQ== + dependencies: + "@babel/runtime" "^7.23.5" + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -13929,6 +15241,11 @@ tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== +tiny-parse-argv@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tiny-parse-argv/-/tiny-parse-argv-2.4.0.tgz#8612163a88104a5af9a64e4775cd1e091d4fa265" + integrity sha512-WTEsnmeHNr99hLQIDA+gnsS+fDsCDITlqgI+zEhx9M6ErPt0heoNZ1PGvql6wcf95sIx40J0MLYXaPveGwtpoA== + tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -14165,6 +15482,11 @@ typescript@^5.1.0: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@^5.2.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + ua-parser-js@^1.0.33: version "1.0.35" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.35.tgz#c4ef44343bc3db0a3cbefdf21822f1b1fc1ab011" @@ -14175,6 +15497,11 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== +ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496" + integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== + uint8arrays@^3.0.0, uint8arrays@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" @@ -14192,6 +15519,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uncrypto@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b" + integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -14204,6 +15536,17 @@ undici@^5.24.0: dependencies: "@fastify/busboy" "^2.0.0" +unenv@^1.7.4: + version "1.8.0" + resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.8.0.tgz#0f860d5278405700bd95d47b23bc01f3a735d68c" + integrity sha512-uIGbdCWZfhRRmyKj1UioCepQ0jpq638j/Cf0xFTn4zD1nGJ2lSdzYHLzfdXN791oo/0juUiSWW1fBklXMTsuqg== + dependencies: + consola "^3.2.3" + defu "^6.1.3" + mime "^3.0.0" + node-fetch-native "^1.4.1" + pathe "^1.1.1" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -14242,6 +15585,32 @@ unraw@^3.0.0: resolved "https://registry.yarnpkg.com/unraw/-/unraw-3.0.0.tgz#73443ed70d2ab09ccbac2b00525602d5991fbbe3" integrity sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg== +unstorage@^1.9.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.10.1.tgz#bf8cc00a406e40a6293e893da9807057d95875b0" + integrity sha512-rWQvLRfZNBpF+x8D3/gda5nUCQL2PgXy2jNG4U7/Rc9BGEv9+CAJd0YyGCROUBKs9v49Hg8huw3aih5Bf5TAVw== + dependencies: + anymatch "^3.1.3" + chokidar "^3.5.3" + destr "^2.0.2" + h3 "^1.8.2" + ioredis "^5.3.2" + listhen "^1.5.5" + lru-cache "^10.0.2" + mri "^1.2.0" + node-fetch-native "^1.4.1" + ofetch "^1.3.3" + ufo "^1.3.1" + +untun@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/untun/-/untun-0.1.3.tgz#5d10dee37a3a5737ff03d158be877dae0a0e58a6" + integrity sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ== + dependencies: + citty "^0.1.5" + consola "^3.2.3" + pathe "^1.1.1" + update-browserslist-db@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" @@ -14250,6 +15619,11 @@ update-browserslist-db@^1.0.9: escalade "^3.1.1" picocolors "^1.0.0" +uqr@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d" + integrity sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -14340,15 +15714,38 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" -valtio@1.10.6: - version "1.10.6" - resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.10.6.tgz#80ed00198b949939863a0fa56ae687abb417fc4f" - integrity sha512-SxN1bHUmdhW6V8qsQTpCgJEwp7uHbntuH0S9cdLQtiohuevwBksbpXjwj5uDMA7bLwg1WKyq9sEpZrx3TIMrkA== +valtio@1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.11.2.tgz#b8049c02dfe65620635d23ebae9121a741bb6530" + integrity sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw== dependencies: proxy-compare "2.5.1" use-sync-external-store "1.2.0" -viem@^1.0.0, viem@^1.1.8: +valtio@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.12.1.tgz#741f8bd46586f8c4b1a7639b1348252a052b746e" + integrity sha512-R0V4H86Xi2Pp7pmxN/EtV4Q6jr6PMN3t1IwxEvKUp6160r8FimvPh941oWyeK1iec/DTsh9Jb3Q+GputMS8SYg== + dependencies: + derive-valtio "0.1.0" + proxy-compare "2.5.1" + use-sync-external-store "1.2.0" + +viem@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.20.1.tgz#ea92f9bab2fded4be556be4d4be724805d11780e" + integrity sha512-PaYyfuCVkNFzdaVoV8HefMIt5YtjIG6UPqxOz0rYchN52cD05YcRJlO0h/XrehQfo8ib2RSOkZ0ChJ6Adlh1Jg== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "0.9.8" + isows "1.0.3" + ws "8.13.0" + +viem@^1.0.0: version "1.2.5" resolved "https://registry.yarnpkg.com/viem/-/viem-1.2.5.tgz#e9f80a57b80d8749624b4b31a98041549a25c5ad" integrity sha512-AeQ1hiyPKXnWb/KTsxRLt7KZUmLgd6NTCe/GyQf+8TZO7ndXmSZf88swE+60v1bLT+FDUWXufKGJ2oNT129wXw== @@ -14397,6 +15794,17 @@ vscode-languageserver-types@^3.17.1: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== +vue@>=3: + version "3.3.11" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.11.tgz#898d97025f73cdb5fc4e3ae3fd07a54615232140" + integrity sha512-d4oBctG92CRO1cQfVBZp6WJAs0n8AK4Xf5fNjQCBeKCvMI1efGQ5E3Alt1slFJS9fZuPcFoiAiqFvQlv1X7t/w== + dependencies: + "@vue/compiler-dom" "3.3.11" + "@vue/compiler-sfc" "3.3.11" + "@vue/runtime-dom" "3.3.11" + "@vue/server-renderer" "3.3.11" + "@vue/shared" "3.3.11" + w3c-xmlserializer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" @@ -14404,15 +15812,15 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" -wagmi@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-1.3.3.tgz#7c97e44422b1a87d4b7fe4004044b0cc30cebcdb" - integrity sha512-9xZxyH2eg4dTxgPBD1FYCLbJUL9W0SKHoIhTE8pd89fArx1D2nbuFH6kalpSi57CfH00BO0RPF2lx/5dVF93cA== +wagmi@1.4.12: + version "1.4.12" + resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-1.4.12.tgz#e5d31c6d7621ecd9e32eded6c7b1041201223127" + integrity sha512-QRxpjhdMlZmbYTfn9VQkQMKq+l3kwA1O7tF10vaykPrjbGX+IIlyn72ib9oqW9BfQO7n/Sf/mnVz1zbxRhGPWA== dependencies: "@tanstack/query-sync-storage-persister" "^4.27.1" "@tanstack/react-query" "^4.28.0" "@tanstack/react-query-persist-client" "^4.28.0" - "@wagmi/core" "1.3.3" + "@wagmi/core" "1.4.12" abitype "0.8.7" use-sync-external-store "^1.2.0" @@ -14559,9 +15967,18 @@ which@^2.0.1, which@^2.0.2: isexe "^2.0.0" word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^6.2.0: version "6.2.0" @@ -14572,14 +15989,14 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" wrappy@1: version "1.0.2" @@ -14594,11 +16011,21 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@8.12.0, ws@^8.5.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== +ws@8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + ws@^7.3.1, ws@^7.4.5, ws@^7.5.1: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" @@ -14755,6 +16182,11 @@ zenscroll@^4.0.2: resolved "https://registry.yarnpkg.com/zenscroll/-/zenscroll-4.0.2.tgz#e8d5774d1c0738a47bcfa8729f3712e2deddeb25" integrity sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg== +zod@3.22.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== + zustand@^4.3.1: version "4.3.8" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.8.tgz#37113df8e9e1421b0be1b2dca02b49b76210e7c4"