diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 40ef71d349..9496bb37e6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -95,6 +95,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup node uses: actions/setup-node@v4 @@ -115,7 +117,7 @@ jobs: run: yarn --frozen-lockfile --ignore-optional - name: Run Jest - run: yarn test:jest --onlyChanged=${{ github.event_name == 'pull_request' }} --passWithNoTests + run: yarn test:jest ${{ github.event_name == 'pull_request' && '--changedSince=origin/main' || '' }} --passWithNoTests pw_affected_tests: name: Resolve affected Playwright tests diff --git a/.gitignore b/.gitignore index c877d7bbea..721584d133 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ .DS_Store *.pem .tools +grafana # debug npm-debug.log* diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c0b016dabe..ad9f96bd7f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -343,6 +343,7 @@ "sepolia", "polygon", "zkevm", + "zksync", "gnosis", "rootstock", "stability", diff --git a/Dockerfile b/Dockerfile index f38d68ebaa..5448b274ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,8 @@ # ***************************** FROM node:20.11.0-alpine AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat python3 make g++ +RUN ln -sf /usr/bin/python3 /usr/bin/python ### APP # Install dependencies diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index 0785ab160f..f9860d1afc 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -1,7 +1,7 @@ import type { Feature } from './types'; import type { AdButlerConfig } from 'types/client/adButlerConfig'; import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; -import type { AdBannerProviders } from 'types/client/adProviders'; +import type { AdBannerProviders, AdBannerAdditionalProviders } from 'types/client/adProviders'; import { getEnvValue, parseEnvJson } from '../utils'; @@ -11,6 +11,8 @@ const provider: AdBannerProviders = (() => { return envValue && SUPPORTED_AD_BANNER_PROVIDERS.includes(envValue) ? envValue : 'slise'; })(); +const additionalProvider = getEnvValue('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER') as AdBannerAdditionalProviders; + const title = 'Banner ads'; type AdsBannerFeaturePayload = { @@ -23,6 +25,15 @@ type AdsBannerFeaturePayload = { mobile: AdButlerConfig; }; }; +} | { + provider: Exclude; + additionalProvider: 'adbutler'; + adButler: { + config: { + desktop: AdButlerConfig; + mobile: AdButlerConfig; + }; + }; } const config: Feature = (() => { @@ -44,6 +55,24 @@ const config: Feature = (() => { }); } } else if (provider !== 'none') { + + if (additionalProvider === 'adbutler') { + const desktopConfig = parseEnvJson(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP')); + const mobileConfig = parseEnvJson(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE')); + + return Object.freeze({ + title, + isEnabled: true, + provider, + additionalProvider, + adButler: { + config: { + desktop: desktopConfig, + mobile: mobileConfig, + }, + }, + }); + } return Object.freeze({ title, isEnabled: true, diff --git a/configs/app/features/dataAvailability.ts b/configs/app/features/dataAvailability.ts new file mode 100644 index 0000000000..add9e5fec5 --- /dev/null +++ b/configs/app/features/dataAvailability.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'Data availability'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index e2aadfda04..fbe1a6a949 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -6,6 +6,7 @@ export { default as beaconChain } from './beaconChain'; export { default as bridgedTokens } from './bridgedTokens'; export { default as blockchainInteraction } from './blockchainInteraction'; export { default as csvExport } from './csvExport'; +export { default as dataAvailability } from './dataAvailability'; export { default as gasTracker } from './gasTracker'; export { default as googleAnalytics } from './googleAnalytics'; export { default as graphqlApiDocs } from './graphqlApiDocs'; diff --git a/configs/app/features/marketplace.ts b/configs/app/features/marketplace.ts index 288c14a125..b136ce0d09 100644 --- a/configs/app/features/marketplace.ts +++ b/configs/app/features/marketplace.ts @@ -10,13 +10,19 @@ const submitFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM'); const suggestIdeasFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM'); const categoriesUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL'); const adminServiceApiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST'); +const securityReportsUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL'); const title = 'Marketplace'; const config: Feature<( { configUrl: string } | { api: { endpoint: string; basePath: string } } -) & { submitFormUrl: string; categoriesUrl: string | undefined; suggestIdeasFormUrl: string | undefined } +) & { + submitFormUrl: string; + categoriesUrl: string | undefined; + suggestIdeasFormUrl: string | undefined; + securityReportsUrl: string | undefined; +} > = (() => { if (enabled === 'true' && chain.rpcUrl && submitFormUrl) { if (configUrl) { @@ -27,6 +33,7 @@ const config: Feature<( submitFormUrl, categoriesUrl, suggestIdeasFormUrl, + securityReportsUrl, }); } else if (adminServiceApiHost) { return Object.freeze({ @@ -35,6 +42,7 @@ const config: Feature<( submitFormUrl, categoriesUrl, suggestIdeasFormUrl, + securityReportsUrl, api: { endpoint: adminServiceApiHost, basePath: '', diff --git a/configs/app/features/sol2uml.ts b/configs/app/features/sol2uml.ts index 06853e62d8..5a0ac2d4be 100644 --- a/configs/app/features/sol2uml.ts +++ b/configs/app/features/sol2uml.ts @@ -1,5 +1,7 @@ import type { Feature } from './types'; +import stripTrailingSlash from 'lib/stripTrailingSlash'; + import { getEnvValue } from '../utils'; const apiEndpoint = getEnvValue('NEXT_PUBLIC_VISUALIZE_API_HOST'); @@ -13,7 +15,7 @@ const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => isEnabled: true, api: { endpoint: apiEndpoint, - basePath: '', + basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_VISUALIZE_API_BASE_PATH') || ''), }, }); } diff --git a/configs/app/features/stats.ts b/configs/app/features/stats.ts index b05f2f9659..d3a90ce061 100644 --- a/configs/app/features/stats.ts +++ b/configs/app/features/stats.ts @@ -1,5 +1,7 @@ import type { Feature } from './types'; +import stripTrailingSlash from 'lib/stripTrailingSlash'; + import { getEnvValue } from '../utils'; const apiEndpoint = getEnvValue('NEXT_PUBLIC_STATS_API_HOST'); @@ -13,7 +15,7 @@ const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => isEnabled: true, api: { endpoint: apiEndpoint, - basePath: '', + basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_STATS_API_BASE_PATH') || ''), }, }); } diff --git a/configs/app/meta.ts b/configs/app/meta.ts index cf0f309534..70930b2fe3 100644 --- a/configs/app/meta.ts +++ b/configs/app/meta.ts @@ -1,13 +1,13 @@ import app from './app'; import { getEnvValue, getExternalAssetFilePath } from './utils'; -const defaultImageUrl = app.baseUrl + '/static/og_placeholder.png'; +const defaultImageUrl = '/static/og_placeholder.png'; const meta = Object.freeze({ promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') || 'true', og: { description: getEnvValue('NEXT_PUBLIC_OG_DESCRIPTION') || '', - imageUrl: getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl, + imageUrl: app.baseUrl + (getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl), }, }); diff --git a/configs/app/ui/views/tx.ts b/configs/app/ui/views/tx.ts index 4c45a565f0..f725363504 100644 --- a/configs/app/ui/views/tx.ts +++ b/configs/app/ui/views/tx.ts @@ -1,5 +1,5 @@ -import type { TxAdditionalFieldsId, TxFieldsId, TxViewId } from 'types/views/tx'; -import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS, TX_VIEWS_IDS } from 'types/views/tx'; +import type { TxAdditionalFieldsId, TxFieldsId } from 'types/views/tx'; +import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from 'types/views/tx'; import { getEnvValue, parseEnvJson } from 'configs/app/utils'; @@ -33,31 +33,9 @@ const additionalFields = (() => { return result; })(); -const hiddenViews = (() => { - const envValue = getEnvValue('NEXT_PUBLIC_VIEWS_TX_HIDDEN_VIEWS'); - - if (!envValue) { - return undefined; - } - - const parsedValue = parseEnvJson>(envValue); - - if (!Array.isArray(parsedValue)) { - return undefined; - } - - const result = TX_VIEWS_IDS.reduce((result, item) => { - result[item] = parsedValue.includes(item); - return result; - }, {} as Record); - - return result; -})(); - const config = Object.freeze({ hiddenFields, additionalFields, - hiddenViews, }); export default config; diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 9fc1c00433..2715974d4b 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -46,6 +46,12 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_AD_BANNER_PROVIDER=hype NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +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.jpg?raw=true diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 2d003f5930..48eee58328 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -48,6 +48,7 @@ NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_MARKETPLACE_ENABLED=true NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://gist.githubusercontent.com/maxaleks/ce5c7e3de53e8f5b240b88265daf5839/raw/328383c958a8f7ecccf6d50c953bcdf8ab3faa0a/security_reports_goerli_test.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com diff --git a/configs/envs/.env.optimism_goerli b/configs/envs/.env.optimism_goerli index d34f132149..c20a941ab9 100644 --- a/configs/envs/.env.optimism_goerli +++ b/configs/envs/.env.optimism_goerli @@ -43,6 +43,6 @@ 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 # rollup -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true -NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw -NEXT_PUBLIC_L1_BASE_URL=https://eth-goerli.blockscout.com/ +NEXT_PUBLIC_ROLLUP_TYPE='optimistic' +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-goerli.blockscout.com/ \ No newline at end of file diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index 1b4a4349e5..223abf5f32 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -17,6 +17,7 @@ NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation # api configuration +NEXT_PUBLIC_API_PROTOCOL=http NEXT_PUBLIC_API_HOST=localhost NEXT_PUBLIC_API_PORT=3003 NEXT_PUBLIC_API_BASE_PATH=/ @@ -47,7 +48,8 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx -NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004 -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005 -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006 +NEXT_PUBLIC_STATS_API_HOST=http://localhost:3004 +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=http://localhost:3005 +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=http://localhost:3006 NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx \ No newline at end of file diff --git a/configs/envs/.env.sepolia b/configs/envs/.env.sepolia index 25d7329dc9..0b837f2f1b 100644 --- a/configs/envs/.env.sepolia +++ b/configs/envs/.env.sepolia @@ -57,6 +57,8 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_HAS_BEACON_CHAIN=true NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_AD_BANNER_PROVIDER=getit +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global #meta NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png diff --git a/configs/envs/.env.zksync b/configs/envs/.env.zksync new file mode 100644 index 0000000000..6693f0ca4e --- /dev/null +++ b/configs/envs/.env.zksync @@ -0,0 +1,59 @@ +# Set of ENVs for zkSync (dev only) +# https://zksync.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=ZkSync Era +NEXT_PUBLIC_NETWORK_SHORT_NAME=ZkSync Era +NEXT_PUBLIC_NETWORK_ID=324 +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://mainnet.era.zksync.io + +# api configuration +NEXT_PUBLIC_API_HOST=zksync.blockscout.com +NEXT_PUBLIC_API_PORT=80 +NEXT_PUBLIC_API_PROTOCOL=http +NEXT_PUBLIC_API_BASE_PATH=/ + +# ui config +## homepage +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +## sidebar +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/polygon-mainnet.json +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zksync.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zksync-dark.svg +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zksync-short.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zksync-short-dark.svg +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(53, 103, 246, 1)' +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(255, 255, 255, 1)' +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://zksync.drpc.org?ref=559183','text':'Public RPC'}] +## footer +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/zksync.json +## views +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=false +## misc +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'l2scan','baseUrl':'https://zksync-era.l2scan.co/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zksync.png +# app features +NEXT_PUBLIC_APP_INSTANCE=local +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x79c7802ccdf3be5a49c47cc751aad351b0027e8275f6f54878eda50ee559a648 +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws +NEXT_PUBLIC_LOGOUT_URL=https://zksync.us.auth0.com/v2/logout +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_SAFE_TX_SERVICE_URL=https://safe-transaction-zksync.safe.global +# rollup +NEXT_PUBLIC_ROLLUP_TYPE=zkSync +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index f80d043ad0..cc9f4a09a7 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -16,6 +16,7 @@ ASSETS_DIR="$1" ASSETS_ENVS=( "NEXT_PUBLIC_MARKETPLACE_CONFIG_URL" "NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL" + "NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL" "NEXT_PUBLIC_FEATURED_NETWORKS" "NEXT_PUBLIC_FOOTER_LINKS" "NEXT_PUBLIC_NETWORK_LOGO" diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 105efca7e1..9770ea6399 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -37,6 +37,7 @@ async function validateEnvs(appEnvs: Record) { 'NEXT_PUBLIC_FEATURED_NETWORKS', 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL', + 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', 'NEXT_PUBLIC_FOOTER_LINKS', ]; diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 5e4f7712e4..de24cbb3ef 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -9,12 +9,12 @@ declare module 'yup' { import * as yup from 'yup'; import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; -import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders'; -import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders'; +import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS, SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS } from '../../../types/client/adProviders'; +import type { AdTextProviders, AdBannerProviders, AdBannerAdditionalProviders } from '../../../types/client/adProviders'; import type { ContractCodeIde } from '../../../types/client/contract'; import { GAS_UNITS } from '../../../types/client/gasTracker'; import type { GasUnit } from '../../../types/client/gasTracker'; -import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; +import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace'; import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; import { ROLLUP_TYPES } from '../../../types/client/rollup'; @@ -32,8 +32,8 @@ import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address import { BLOCK_FIELDS_IDS } from '../../../types/views/block'; import type { BlockFieldId } from '../../../types/views/block'; import type { NftMarketplaceItem } from '../../../types/views/nft'; -import type { TxAdditionalFieldsId, TxFieldsId, TxViewId } from '../../../types/views/tx'; -import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS, TX_VIEWS_IDS } from '../../../types/views/tx'; +import type { TxAdditionalFieldsId, TxFieldsId } from '../../../types/views/tx'; +import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx'; import { replaceQuotes } from '../../../configs/app/utils'; import * as regexp from '../../../lib/regexp'; @@ -85,6 +85,65 @@ const marketplaceAppSchema: yup.ObjectSchema = yup priority: yup.number(), }); +const issueSeverityDistributionSchema: yup.ObjectSchema = yup + .object({ + critical: yup.number().required(), + gas: yup.number().required(), + high: yup.number().required(), + informational: yup.number().required(), + low: yup.number().required(), + medium: yup.number().required(), + }); + +const solidityscanReportSchema: yup.ObjectSchema = yup + .object({ + contractname: yup.string().required(), + scan_status: yup.string().required(), + scan_summary: yup + .object({ + issue_severity_distribution: issueSeverityDistributionSchema.required(), + lines_analyzed_count: yup.number().required(), + scan_time_taken: yup.number().required(), + score: yup.string().required(), + score_v2: yup.string().required(), + threat_score: yup.string().required(), + }) + .required(), + scanner_reference_url: yup.string().test(urlTest).required(), + }); + +const contractDataSchema: yup.ObjectSchema = yup + .object({ + address: yup.string().required(), + isVerified: yup.boolean().required(), + solidityScanReport: solidityscanReportSchema.nullable().notRequired(), + }); + +const chainsDataSchema = yup.lazy((objValue) => { + let schema = yup.object(); + Object.keys(objValue).forEach((key) => { + schema = schema.shape({ + [key]: yup.object({ + overallInfo: yup.object({ + verifiedNumber: yup.number().required(), + totalContractsNumber: yup.number().required(), + solidityScanContractsNumber: yup.number().required(), + securityScore: yup.number().required(), + issueSeverityDistribution: issueSeverityDistributionSchema.required(), + }).required(), + contractsData: yup.array().of(contractDataSchema).required(), + }), + }); + }); + return schema; +}); + +const securityReportSchema: yup.ObjectSchema = yup + .object({ + appName: yup.string().required(), + chainsData: chainsDataSchema, + }); + const marketplaceSchema = yup .object() .shape({ @@ -125,6 +184,16 @@ const marketplaceSchema = yup // eslint-disable-next-line max-len otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), }), + NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL: yup + .array() + .json() + .of(securityReportSchema) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), }); const beaconChainSchema = yup @@ -176,12 +245,23 @@ const adButlerConfigSchema = yup height: yup.number().positive().required(), }) .required(), + }) + .when('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER', { + is: (value: AdBannerProviders) => value === 'adbutler', + then: (schema) => schema + .shape({ + id: yup.string().required(), + width: yup.number().positive().required(), + height: yup.number().positive().required(), + }) + .required(), }); const adsBannerSchema = yup .object() .shape({ NEXT_PUBLIC_AD_BANNER_PROVIDER: yup.string().oneOf(SUPPORTED_AD_BANNER_PROVIDERS), + NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: yup.string().oneOf(SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS), NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: adButlerConfigSchema, NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema, }); @@ -448,11 +528,6 @@ const schema = yup .transform(replaceQuotes) .json() .of(yup.string().oneOf(TX_ADDITIONAL_FIELDS_IDS)), - NEXT_PUBLIC_VIEWS_TX_HIDDEN_VIEWS: yup - .array() - .transform(replaceQuotes) - .json() - .of(yup.string().oneOf(TX_VIEWS_IDS)), NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: yup .array() .transform(replaceQuotes) @@ -478,7 +553,9 @@ const schema = yup // 5. Features configuration NEXT_PUBLIC_API_SPEC_URL: yup.string().test(urlTest), NEXT_PUBLIC_STATS_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_STATS_API_BASE_PATH: yup.string(), NEXT_PUBLIC_VISUALIZE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_VISUALIZE_API_BASE_PATH: yup.string(), NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_NAME_SERVICE_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup.string().matches(regexp.HEX_REGEXP), @@ -508,6 +585,7 @@ const schema = yup NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE: yup.string().oneOf(VALIDATORS_CHAIN_TYPE), NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(), NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string().oneOf(GAS_UNITS)), + NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: yup.boolean(), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), diff --git a/deploy/tools/envs-validator/test/.env.adbutler_add b/deploy/tools/envs-validator/test/.env.adbutler_add new file mode 100644 index 0000000000..7f1968e4bb --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.adbutler_add @@ -0,0 +1,4 @@ +NEXT_PUBLIC_AD_BANNER_PROVIDER='slise' +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER='adbutler' +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'123456','width':'728','height':'90'} +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'654321','width':'300','height':'100'} \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 6c6f417c96..432d4973b7 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -11,6 +11,7 @@ NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS=[{'id':'1','title':'Ethereum','short_title':'E NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES=[{'type':'omni','title':'OmniBridge','short_title':'OMNI'}] NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://example.com/icon.svg'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://example.com +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com NEXT_PUBLIC_FOOTER_LINKS=https://example.com NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d @@ -43,6 +44,7 @@ NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://blockscout.com','text':'Blockscout'}] NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE=true NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global NEXT_PUBLIC_STATS_API_HOST=https://example.com +NEXT_PUBLIC_STATS_API_BASE_PATH=/ NEXT_PUBLIC_USE_NEXT_JS_PROXY=false NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts'] @@ -50,8 +52,8 @@ NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward'] NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'NFT Marketplace','collection_url':'https://example.com/{hash}','instance_url':'https://example.com/{hash}/{id}','logo_url':'https://example.com/logo.png'}] NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS=['fee_per_gas'] NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee','gas_fees','burnt_fees'] -NEXT_PUBLIC_VIEWS_TX_HIDDEN_VIEWS=['blob_txs'] NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com +NEXT_PUBLIC_VISUALIZE_API_BASE_PATH=https://example.com NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket'] NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap diff --git a/deploy/tools/envs-validator/test/.env.marketplace b/deploy/tools/envs-validator/test/.env.marketplace index 316dd70bd1..eaf12c6dc9 100644 --- a/deploy/tools/envs-validator/test/.env.marketplace +++ b/deploy/tools/envs-validator/test/.env.marketplace @@ -3,4 +3,5 @@ NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://example.com +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://example.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com diff --git a/deploy/tools/envs-validator/test/assets/marketplace_security_reports.json b/deploy/tools/envs-validator/test/assets/marketplace_security_reports.json new file mode 100644 index 0000000000..cf0f481ae3 --- /dev/null +++ b/deploy/tools/envs-validator/test/assets/marketplace_security_reports.json @@ -0,0 +1,1073 @@ +[ + { + "appName": "paraswap", + "doc": "https://developers.paraswap.network/smart-contracts", + "chainsData": { + "1": { + "overallInfo": { + "verifiedNumber": 4, + "totalContractsNumber": 4, + "solidityScanContractsNumber": 4, + "securityScore": 77.41749999999999, + "issueSeverityDistribution": { + "critical": 5, + "gas": 58, + "high": 9, + "informational": 27, + "low": 41, + "medium": 5 + } + }, + "contractsData": [ + { + "address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contractname": "AugustusSwapper", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 8, + "high": 4, + "informational": 7, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 180, + "scan_time_taken": 1, + "score": "3.61", + "score_v2": "72.22", + "threat_score": "73.68" + } + } + }, + { + "address": "0x216b4b4ba9f3e719726886d34a177484278bfcae", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x216b4b4ba9f3e719726886d34a177484278bfcae", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x216b4b4ba9f3e719726886d34a177484278bfcae", + "contractname": "TokenTransferProxy", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x216b4b4ba9f3e719726886d34a177484278bfcae/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 1, + "gas": 29, + "high": 5, + "informational": 14, + "low": 21, + "medium": 3 + }, + "lines_analyzed_count": 553, + "scan_time_taken": 1, + "score": "3.92", + "score_v2": "78.48", + "threat_score": "78.95" + } + } + }, + { + "address": "0xa68bEA62Dc4034A689AA0F58A76681433caCa663", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xa68bEA62Dc4034A689AA0F58A76681433caCa663", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xa68bEA62Dc4034A689AA0F58A76681433caCa663", + "contractname": "AugustusRegistry", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xa68bEA62Dc4034A689AA0F58A76681433caCa663/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 3, + "high": 0, + "informational": 5, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 103, + "scan_time_taken": 0, + "score": "4.22", + "score_v2": "84.47", + "threat_score": "88.89" + } + } + }, + { + "address": "0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7", + "contractname": "FeeClaimer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 18, + "high": 0, + "informational": 1, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 149, + "scan_time_taken": 0, + "score": "3.72", + "score_v2": "74.50", + "threat_score": "94.74" + } + } + } + ] + }, + "10": { + "overallInfo": { + "verifiedNumber": 3, + "totalContractsNumber": 4, + "solidityScanContractsNumber": 3, + "securityScore": 75.44333333333333, + "issueSeverityDistribution": { + "critical": 4, + "gas": 29, + "high": 4, + "informational": 20, + "low": 20, + "medium": 2 + } + }, + "contractsData": [ + { + "address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contractname": "AugustusSwapper", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 8, + "high": 4, + "informational": 7, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 180, + "scan_time_taken": 1, + "score": "3.61", + "score_v2": "72.22", + "threat_score": "73.68" + } + } + }, + { + "address": "0x216B4B4Ba9F3e719726886d34a177484278Bfcae", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x6e7bE86000dF697facF4396efD2aE2C322165dC3", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x6e7bE86000dF697facF4396efD2aE2C322165dC3", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x6e7bE86000dF697facF4396efD2aE2C322165dC3", + "contractname": "AugustusRegistry", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x6e7bE86000dF697facF4396efD2aE2C322165dC3/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 3, + "high": 0, + "informational": 5, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 102, + "scan_time_taken": 0, + "score": "4.22", + "score_v2": "84.31", + "threat_score": "88.89" + } + } + }, + { + "address": "0xA7465CCD97899edcf11C56D2d26B49125674e45F", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xA7465CCD97899edcf11C56D2d26B49125674e45F", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xA7465CCD97899edcf11C56D2d26B49125674e45F", + "contractname": "FeeClaimer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xA7465CCD97899edcf11C56D2d26B49125674e45F/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 18, + "high": 0, + "informational": 8, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 149, + "scan_time_taken": 1, + "score": "3.49", + "score_v2": "69.80", + "threat_score": "94.74" + } + } + } + ] + }, + "8453": { + "overallInfo": { + "verifiedNumber": 1, + "totalContractsNumber": 4, + "solidityScanContractsNumber": 1, + "securityScore": 73.33, + "issueSeverityDistribution": { + "critical": 4, + "gas": 8, + "high": 4, + "informational": 5, + "low": 8, + "medium": 1 + } + }, + "contractsData": [ + { + "address": "0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52", + "contractname": "AugustusSwapper", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 8, + "high": 4, + "informational": 5, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 180, + "scan_time_taken": 1, + "score": "3.67", + "score_v2": "73.33", + "threat_score": "73.68" + } + } + }, + { + "address": "0x93aAAe79a53759cD164340E4C8766E4Db5331cD7", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x7e31b336f9e8ba52ba3c4ac861b033ba90900bb3", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x9aaB4B24541af30fD72784ED98D8756ac0eFb3C7", + "isVerified": false, + "solidityScanReport": null + } + ] + } + } + }, + { + "appName": "mean-finance", + "doc": "https://docs.mean.finance/guides/smart-contract-registry", + "chainsData": { + "1": { + "overallInfo": { + "verifiedNumber": 4, + "totalContractsNumber": 6, + "solidityScanContractsNumber": 4, + "securityScore": 61.36750000000001, + "issueSeverityDistribution": { + "critical": 6, + "gas": 25, + "high": 1, + "informational": 10, + "low": 20, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x20bdAE1413659f47416f769a4B27044946bc9923", + "contractname": "DCAPermissionsManager", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x20bdAE1413659f47416f769a4B27044946bc9923/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 2, + "gas": 22, + "high": 0, + "informational": 8, + "low": 11, + "medium": 3 + }, + "lines_analyzed_count": 314, + "scan_time_taken": 1, + "score": "3.87", + "score_v2": "77.39", + "threat_score": "88.89" + } + } + }, + { + "address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contractname": "DCAHubPositionDescriptor", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 1, + "informational": 2, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 280, + "scan_time_taken": 1, + "score": "4.77", + "score_v2": "95.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 11, + "scan_time_taken": 0, + "score": "1.82", + "score_v2": "36.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 11, + "scan_time_taken": 0, + "score": "1.82", + "score_v2": "36.36", + "threat_score": "100.00" + } + } + } + ] + }, + "10": { + "overallInfo": { + "verifiedNumber": 5, + "totalContractsNumber": 6, + "solidityScanContractsNumber": 5, + "securityScore": 66.986, + "issueSeverityDistribution": { + "critical": 6, + "gas": 26, + "high": 1, + "informational": 10, + "low": 23, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contractname": "DCAHub", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 23, + "scan_time_taken": 0, + "score": "3.48", + "score_v2": "69.57", + "threat_score": "94.44" + } + } + }, + { + "address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x20bdAE1413659f47416f769a4B27044946bc9923", + "contractname": "DCAPermissionsManager", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x20bdAE1413659f47416f769a4B27044946bc9923/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 2, + "gas": 22, + "high": 0, + "informational": 8, + "low": 11, + "medium": 3 + }, + "lines_analyzed_count": 314, + "scan_time_taken": 1, + "score": "3.87", + "score_v2": "77.39", + "threat_score": "88.89" + } + } + }, + { + "address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 16, + "scan_time_taken": 0, + "score": "2.81", + "score_v2": "56.25", + "threat_score": "100.00" + } + } + }, + { + "address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contractname": "DCAHubPositionDescriptor", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 1, + "informational": 2, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 280, + "scan_time_taken": 1, + "score": "4.77", + "score_v2": "95.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 11, + "scan_time_taken": 0, + "score": "1.82", + "score_v2": "36.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "isVerified": false, + "solidityScanReport": null + } + ] + }, + "8453": { + "overallInfo": { + "verifiedNumber": 4, + "totalContractsNumber": 6, + "solidityScanContractsNumber": 4, + "securityScore": 74.88, + "issueSeverityDistribution": { + "critical": 6, + "gas": 25, + "high": 1, + "informational": 7, + "low": 20, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contractname": "DCAHub", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 23, + "scan_time_taken": 0, + "score": "3.48", + "score_v2": "69.57", + "threat_score": "94.44" + } + } + }, + { + "address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0x20bdAE1413659f47416f769a4B27044946bc9923", + "contractname": "DCAPermissionsManager", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x20bdAE1413659f47416f769a4B27044946bc9923/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 2, + "gas": 22, + "high": 0, + "informational": 5, + "low": 11, + "medium": 3 + }, + "lines_analyzed_count": 314, + "scan_time_taken": 1, + "score": "3.92", + "score_v2": "78.34", + "threat_score": "88.89" + } + } + }, + { + "address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 16, + "scan_time_taken": 0, + "score": "2.81", + "score_v2": "56.25", + "threat_score": "100.00" + } + } + }, + { + "address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contractname": "DCAHubPositionDescriptor", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 1, + "informational": 2, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 280, + "scan_time_taken": 1, + "score": "4.77", + "score_v2": "95.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "isVerified": false, + "solidityScanReport": null + } + ] + } + } + }, + { + "appName": "cow-swap", + "doc": "https://docs.cow.fi/cow-protocol/reference/contracts/core#deployments", + "chainsData": { + "1": { + "overallInfo": { + "verifiedNumber": 3, + "totalContractsNumber": 3, + "solidityScanContractsNumber": 3, + "securityScore": 87.60000000000001, + "issueSeverityDistribution": { + "critical": 4, + "gas": 18, + "high": 0, + "informational": 13, + "low": 14, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contractname": "GPv2Settlement", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x9008D19f58AAbD9eD0D60971565AA8510560ab41/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 16, + "high": 0, + "informational": 7, + "low": 5, + "medium": 3 + }, + "lines_analyzed_count": 493, + "scan_time_taken": 1, + "score": "4.57", + "score_v2": "91.48", + "threat_score": "94.74" + } + } + }, + { + "address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contractname": "EIP173Proxy", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 0, + "high": 0, + "informational": 4, + "low": 5, + "medium": 0 + }, + "lines_analyzed_count": 94, + "scan_time_taken": 0, + "score": "4.26", + "score_v2": "85.11", + "threat_score": "88.89" + } + } + }, + { + "address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contractname": "GPv2VaultRelayer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 2, + "high": 0, + "informational": 2, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 87, + "scan_time_taken": 0, + "score": "4.31", + "score_v2": "86.21", + "threat_score": "94.74" + } + } + } + ] + }, + "100": { + "overallInfo": { + "verifiedNumber": 3, + "totalContractsNumber": 3, + "solidityScanContractsNumber": 3, + "securityScore": 87.60000000000001, + "issueSeverityDistribution": { + "critical": 4, + "gas": 18, + "high": 0, + "informational": 13, + "low": 14, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contract_chain": "gnosis", + "contract_platform": "blockscout", + "contract_url": "https://gnosis.blockscout.com/address/0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contractname": "GPv2Settlement", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x9008D19f58AAbD9eD0D60971565AA8510560ab41/blockscout/gnosis?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 16, + "high": 0, + "informational": 7, + "low": 5, + "medium": 3 + }, + "lines_analyzed_count": 493, + "scan_time_taken": 1, + "score": "4.57", + "score_v2": "91.48", + "threat_score": "94.74" + } + } + }, + { + "address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contractname": "EIP173Proxy", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE/blockscout/gnosis?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 0, + "high": 0, + "informational": 4, + "low": 5, + "medium": 0 + }, + "lines_analyzed_count": 94, + "scan_time_taken": 0, + "score": "4.26", + "score_v2": "85.11", + "threat_score": "88.89" + } + } + }, + { + "address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contractname": "GPv2VaultRelayer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110/blockscout/gnosis?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 2, + "high": 0, + "informational": 2, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 87, + "scan_time_taken": 0, + "score": "4.31", + "score_v2": "86.21", + "threat_score": "94.74" + } + } + } + ] + } + } + } +] diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index a703bbc4ae..91313e4b12 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -4,7 +4,7 @@ imagePullSecrets: - name: regcred config: network: - id: 5 + id: 11155111 name: Blockscout shortname: Blockscout currency: @@ -61,11 +61,14 @@ frontend: NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_AUTH_URL: https://blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_MARKETPLACE_ENABLED: true + 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_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form + NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL: https://gist.githubusercontent.com/maxaleks/ce5c7e3de53e8f5b240b88265daf5839/raw/328383c958a8f7ecccf6d50c953bcdf8ab3faa0a/security_reports_goerli_test.json NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" - NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli + NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io + NEXT_PUBLIC_NETWORK_ID: '11155111' NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d @@ -79,9 +82,15 @@ frontend: NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" NEXT_PUBLIC_HAS_USER_OPS: true NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" + NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: noves NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true NEXT_PUBLIC_AD_BANNER_PROVIDER: getit + NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: adbutler + NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: "{ \"id\": \"632019\", \"width\": \"728\", \"height\": \"90\" }" + NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }" + NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true + PROMETHEUS_METRICS_ENABLED: true envFromSecret: NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI diff --git a/docs/ENVS.md b/docs/ENVS.md index fbdff1e706..8802bbc267 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -202,7 +202,7 @@ Settings for meta tags and OG tags | Variable | Type | Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` | +| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Default 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` | @@ -219,7 +219,6 @@ Settings for meta tags and OG tags | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS | `Array` | Array of the transaction fields ids that should be hidden. See below the list of the possible id values. | - | - | `'["value","tx_fee"]'` | | NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS | `Array` | Array of the additional fields ids that should be added to the transaction details. See below the list of the possible id values. | - | - | `'["fee_per_gas"]'` | -| NEXT_PUBLIC_VIEWS_TX_HIDDEN_VIEWS | `Array` | Transaction views that should be hidden. See below the list of the possible id values. | - | - | `'["blob_txs"]'` | ##### Transaction fields list | Id | Description | @@ -236,11 +235,6 @@ Settings for meta tags and OG tags | --- | --- | | `fee_per_gas` | Amount of total fee divided by total amount of gas used by transaction | -##### Transaction view list -| Id | Description | -| --- | --- | -| `blob_txs` | List of all transactions that contain blob data | -   #### NFT views @@ -352,6 +346,7 @@ This feature is **enabled by default** with the `slise` ads provider. To switch | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `hype` \| `getit` \| `none` | Ads provider | - | `slise` | `coinzilla` | +| NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER | `adbutler` | Additional ads provider to mix with the main one | - | - | `adbutler` | | NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` | | NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` | @@ -388,7 +383,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'shibarium' \| 'zkEvm' ` | Rollup chain type | Required | - | `'optimistic'` | +| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | | NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | | NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | - | - | `https://app.optimism.io/bridge/withdraw` | @@ -455,6 +450,7 @@ This feature is **always enabled**, but you can configure its behavior by passin | NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM | `string` | Link to form where users can suggest ideas for the marketplace | - | - | `https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form` | | NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | | NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL | `string` | URL of configuration file (`.json` format only) which contains the list of categories to be displayed on the marketplace page in the specified order. If no URL is provided, then the list of categories will be compiled based on the `categories` fields from the marketplace (apps) configuration file | - | - | `https://example.com/marketplace_categories.json` | +| NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL | `string` | URL of configuration file (`.json` format only) which contains app security reports for displaying security scores on the Marketplace page | - | - | `https://example.com/marketplace_security_reports.json` | #### Marketplace app configuration properties @@ -498,6 +494,7 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_VISUALIZE_API_HOST | `string` | Visualize API endpoint url | Required | - | `https://visualizer.services.blockscout.com` | +| NEXT_PUBLIC_VISUALIZE_API_BASE_PATH | `string` | Base path for Visualize API endpoint url | - | - | `/poa/core` |   @@ -505,7 +502,8 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_STATS_API_HOST | `string` | API endpoint url | Required | - | `https://stats.services.blockscout.com` | +| NEXT_PUBLIC_STATS_API_HOST | `string` | Stats API endpoint url | Required | - | `https://stats.services.blockscout.com` | +| NEXT_PUBLIC_STATS_API_BASE_PATH | `string` | Base path for Stats API endpoint url | - | - | `/poa/core` |   @@ -524,7 +522,7 @@ This feature is **enabled by default** with the `['metamask']` value. To switch | 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` | +| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `noves` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` |   @@ -546,6 +544,16 @@ This feature allows resolving blockchain addresses using human-readable domain n   +### Data Availability + +This feature enables views related to blob transactions (EIP-4844), such as the Blob Txns tab on the Transactions page and the Blob details page. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED | `boolean` | Set to true to enable blob transactions views. | Required | - | `true` | + +  + ### Bridged tokens This feature allows users to view tokens that have been bridged from other EVM chains. Additional tab "Bridged" will be added to the tokens page and the link to original token will be displayed on the token page. diff --git a/global.d.ts b/global.d.ts index 2955f3f872..1632505b52 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,4 +1,4 @@ -import type { WindowProvider } from 'wagmi'; +import type { WalletProvider } from 'types/web3'; type CPreferences = { zone: string; @@ -8,7 +8,7 @@ type CPreferences = { declare global { export interface Window { - ethereum?: WindowProvider; + ethereum?: WalletProvider | undefined; coinzilla_display: Array; ga?: { getAll: () => Array<{ get: (prop: string) => string }>; @@ -27,3 +27,5 @@ declare global { } } } + +export {}; diff --git a/icons/apps_list.svg b/icons/apps_list.svg new file mode 100644 index 0000000000..62cb5020d6 --- /dev/null +++ b/icons/apps_list.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/apps_xs.svg b/icons/apps_xs.svg new file mode 100644 index 0000000000..c3dd9f6867 --- /dev/null +++ b/icons/apps_xs.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/beta.svg b/icons/beta.svg new file mode 100644 index 0000000000..b5db1b60db --- /dev/null +++ b/icons/beta.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/beta_xs.svg b/icons/beta_xs.svg new file mode 100644 index 0000000000..d22d754e68 --- /dev/null +++ b/icons/beta_xs.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/blob.svg b/icons/blob.svg index 9dc2b542ff..523a29bac5 100644 --- a/icons/blob.svg +++ b/icons/blob.svg @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/icons/blobs/image.svg b/icons/blobs/image.svg index dba0e826b8..be08dd269c 100644 --- a/icons/blobs/image.svg +++ b/icons/blobs/image.svg @@ -1,6 +1,6 @@ - + diff --git a/icons/brands/solidity_scan.svg b/icons/brands/solidity_scan.svg new file mode 100644 index 0000000000..ac5747c69a --- /dev/null +++ b/icons/brands/solidity_scan.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/icons/contracts.svg b/icons/contracts.svg new file mode 100644 index 0000000000..68fb55c349 --- /dev/null +++ b/icons/contracts.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/contracts_verified.svg b/icons/contracts_verified.svg new file mode 100644 index 0000000000..dbb95dd759 --- /dev/null +++ b/icons/contracts_verified.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/gas_xl.svg b/icons/gas_xl.svg index a3c436b5d4..5a3913ac16 100644 --- a/icons/gas_xl.svg +++ b/icons/gas_xl.svg @@ -1,3 +1,3 @@ - + diff --git a/icons/gear_slim.svg b/icons/gear_slim.svg new file mode 100644 index 0000000000..abc14e6a78 --- /dev/null +++ b/icons/gear_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/lightning.svg b/icons/lightning.svg index 91b1ae92ca..03fea73d75 100644 --- a/icons/lightning.svg +++ b/icons/lightning.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/social/tweet.svg b/icons/social/tweet.svg deleted file mode 100644 index 20cc63ccc6..0000000000 --- a/icons/social/tweet.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/icons/social/twitter.svg b/icons/social/twitter.svg new file mode 100644 index 0000000000..21e9812ff7 --- /dev/null +++ b/icons/social/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/social/twitter_filled.svg b/icons/social/twitter_filled.svg index 5fc356a969..1850ec857b 100644 --- a/icons/social/twitter_filled.svg +++ b/icons/social/twitter_filled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/star_filled.svg b/icons/star_filled.svg index 10a3cfb0c8..63f95055c9 100644 --- a/icons/star_filled.svg +++ b/icons/star_filled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/star_outline.svg b/icons/star_outline.svg index e6fd05339c..bf6ad8c3e4 100644 --- a/icons/star_outline.svg +++ b/icons/star_outline.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/up.svg b/icons/up.svg new file mode 100644 index 0000000000..375381a790 --- /dev/null +++ b/icons/up.svg @@ -0,0 +1,3 @@ + + + diff --git a/jest/lib.tsx b/jest/lib.tsx index 7097e2b19a..048f26733a 100644 --- a/jest/lib.tsx +++ b/jest/lib.tsx @@ -21,6 +21,7 @@ const PAGE_PROPS = { number: '', q: '', name: '', + adBannerProvider: '', }; const TestApp = ({ children }: {children: React.ReactNode}) => { diff --git a/lib/api/resources.ts b/lib/api/resources.ts index a124e65bbb..0a6aaa0a90 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -56,6 +56,7 @@ import type { import type { IndexingStatus } from 'types/api/indexingStatus'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; +import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves'; import type { OptimisticL2DepositsResponse, OptimisticL2DepositsItem, @@ -96,6 +97,7 @@ 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/zkEvmL2'; +import type { ZkSyncBatch, ZkSyncBatchesResponse, ZkSyncBatchTxs } from 'types/api/zkSyncL2'; import type { MarketplaceAppOverview } from 'types/client/marketplace'; import type { ArrayElement } from 'types/utils'; @@ -560,6 +562,9 @@ export const RESOURCES = { homepage_zkevm_latest_batch: { path: '/api/v2/main-page/zkevm/batches/latest-number', }, + homepage_zksync_latest_batch: { + path: '/api/v2/main-page/zksync/batches/latest-number', + }, // SEARCH quick_search: { @@ -574,7 +579,7 @@ export const RESOURCES = { path: '/api/v2/search/check-redirect', }, - // L2 + // optimistic L2 l2_deposits: { path: '/api/v2/optimism/deposits', filterFields: [], @@ -611,6 +616,7 @@ export const RESOURCES = { path: '/api/v2/optimism/txn-batches/count', }, + // zkEvm L2 zkevm_l2_txn_batches: { path: '/api/v2/zkevm/batches', filterFields: [], @@ -624,12 +630,34 @@ export const RESOURCES = { path: '/api/v2/zkevm/batches/:number', pathParams: [ 'number' as const ], }, + zkevm_l2_txn_batch_txs: { path: '/api/v2/transactions/zkevm-batch/:number', pathParams: [ 'number' as const ], filterFields: [], }, + // zkSync L2 + zksync_l2_txn_batches: { + path: '/api/v2/zksync/batches', + filterFields: [], + }, + + zksync_l2_txn_batches_count: { + path: '/api/v2/zksync/batches/count', + }, + + zksync_l2_txn_batch: { + path: '/api/v2/zksync/batches/:number', + pathParams: [ 'number' as const ], + }, + + zksync_l2_txn_batch_txs: { + path: '/api/v2/transactions/zksync-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + // SHIBARIUM L2 shibarium_deposits: { path: '/api/v2/shibarium/deposits', @@ -649,6 +677,20 @@ export const RESOURCES = { path: '/api/v2/shibarium/withdrawals/count', }, + // NOVES-FI + noves_transaction: { + path: '/api/v2/proxy/noves-fi/transactions/:hash', + pathParams: [ 'hash' as const ], + }, + noves_address_history: { + path: '/api/v2/proxy/noves-fi/addresses/:address/transactions', + pathParams: [ 'address' as const ], + filterFields: [], + }, + noves_describe_txs: { + path: '/api/v2/proxy/noves-fi/transaction-descriptions', + }, + // USER OPS user_ops: { path: '/api/v2/proxy/account-abstraction/operations', @@ -755,9 +797,10 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' | 'shibarium_deposits' | 'shibarium_withdrawals' | 'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' | +'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' | 'withdrawals' | 'address_withdrawals' | 'block_withdrawals' | 'watchlist' | 'private_tags_address' | 'private_tags_tx' | -'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators'; +'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators' | 'noves_address_history'; export type PaginatedResponse = ResourcePayload; @@ -786,6 +829,7 @@ Q extends 'homepage_deposits' ? Array : Q extends 'homepage_zkevm_l2_batches' ? { items: Array } : Q extends 'homepage_indexing_status' ? IndexingStatus : Q extends 'homepage_zkevm_latest_batch' ? number : +Q extends 'homepage_zksync_latest_batch' ? number : Q extends 'stats_counters' ? Counters : Q extends 'stats_lines' ? StatsCharts : Q extends 'stats_line' ? StatsChart : @@ -877,6 +921,10 @@ Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse : Q extends 'shibarium_deposits' ? ShibariumDepositsResponse : Q extends 'shibarium_withdrawals_count' ? number : Q extends 'shibarium_deposits_count' ? number : +Q extends 'zksync_l2_txn_batches' ? ZkSyncBatchesResponse : +Q extends 'zksync_l2_txn_batches_count' ? number : +Q extends 'zksync_l2_txn_batch' ? ZkSyncBatch : +Q extends 'zksync_l2_txn_batch_txs' ? ZkSyncBatchTxs : Q extends 'contract_security_audits' ? SmartContractSecurityAudits : Q extends 'addresses_lookup' ? EnsAddressLookupResponse : Q extends 'domain_info' ? EnsDomainDetailed : @@ -886,6 +934,9 @@ Q extends 'user_ops' ? UserOpsResponse : Q extends 'user_op' ? UserOp : Q extends 'user_ops_account' ? UserOpsAccount : Q extends 'user_op_interpretation'? TxInterpretationResponse : +Q extends 'noves_transaction' ? NovesResponseData : +Q extends 'noves_address_history' ? NovesAccountHistoryResponse : +Q extends 'noves_describe_txs' ? NovesDescribeTxsResponse : never; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/contexts/app.tsx b/lib/contexts/app.tsx index ec0a25e61b..1d7c7bf8c3 100644 --- a/lib/contexts/app.tsx +++ b/lib/contexts/app.tsx @@ -16,6 +16,7 @@ const AppContext = createContext({ number: '', q: '', name: '', + adBannerProvider: '', }); export function AppContextProvider({ children, pageProps }: Props) { diff --git a/lib/cookies.ts b/lib/cookies.ts index 7f3867f9e6..cef6a38a42 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -10,6 +10,7 @@ export enum NAMES { TXS_SORT='txs_sort', COLOR_MODE='chakra-ui-color-mode', COLOR_MODE_HEX='chakra-ui-color-mode-hex', + ADDRESS_IDENTICON_TYPE='address_identicon_type', INDEXING_ALERT='indexing_alert', ADBLOCK_DETECTED='adblock_detected', MIXPANEL_DEBUG='_mixpanel_debug', diff --git a/lib/growthbook/init.ts b/lib/growthbook/init.ts index d98b2b94b7..10674a4fcc 100644 --- a/lib/growthbook/init.ts +++ b/lib/growthbook/init.ts @@ -7,6 +7,7 @@ import { STORAGE_KEY, STORAGE_LIMIT } from './consts'; export interface GrowthBookFeatures { test_value: string; + security_score_exp: boolean; } export const growthBook = (() => { diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index 52f8b0ca2d..f3f40fc038 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -136,6 +136,26 @@ export default function useNavItems(): ReturnType { ensLookup, ].filter(Boolean), ]; + } else if (rollupFeature.isEnabled && rollupFeature.type === 'zkSync') { + blockchainNavItems = [ + [ + txs, + userOps, + blocks, + { + text: 'Txn batches', + nextRoute: { pathname: '/batches' as const }, + icon: 'txn_batches', + isActive: pathname === '/batches' || pathname === '/batches/[number]', + }, + ].filter(Boolean), + [ + topAccounts, + validators, + verifiedContracts, + ensLookup, + ].filter(Boolean), + ]; } else { blockchainNavItems = [ txs, diff --git a/lib/metadata/getApiDataForSocialPreview.ts b/lib/metadata/getApiDataForSocialPreview.ts new file mode 100644 index 0000000000..8357d1e2ef --- /dev/null +++ b/lib/metadata/getApiDataForSocialPreview.ts @@ -0,0 +1,32 @@ +import type { IncomingMessage, ServerResponse } from 'http'; + +import { httpLogger } from 'nextjs/utils/logger'; + +import metrics from 'lib/monitoring/metrics'; + +export default async function getApiDataForSocialPreview(req: IncomingMessage | undefined, res: ServerResponse | undefined, pathname: string) { + if (!req || !res || !metrics) { + return; + } + + const userAgent = req.headers['user-agent']; + + if (!userAgent) { + return; + } + + if (userAgent.toLowerCase().includes('twitter')) { + httpLogger(req, res); + metrics.requestCounter.inc({ route: pathname, bot: 'twitter' }); + } + + if (userAgent.toLowerCase().includes('facebook')) { + httpLogger(req, res); + metrics.requestCounter.inc({ route: pathname, bot: 'facebook' }); + } + + if (userAgent.toLowerCase().includes('telegram')) { + httpLogger(req, res); + metrics.requestCounter.inc({ route: pathname, bot: 'telegram' }); + } +} diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index ef7ef24e26..a195848744 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -48,6 +48,8 @@ const OG_TYPE_DICT: Record = { // service routes, added only to make typescript happy '/login': 'Regular page', + '/api/metrics': 'Regular page', + '/api/log': 'Regular page', '/api/media-type': 'Regular page', '/api/proxy': 'Regular page', '/api/csrf': 'Regular page', diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index c064709b6f..dbaaf5e0fc 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -51,6 +51,8 @@ const TEMPLATE_MAP: Record = { // service routes, added only to make typescript happy '/login': DEFAULT_TEMPLATE, + '/api/metrics': DEFAULT_TEMPLATE, + '/api/log': DEFAULT_TEMPLATE, '/api/media-type': DEFAULT_TEMPLATE, '/api/proxy': DEFAULT_TEMPLATE, '/api/csrf': DEFAULT_TEMPLATE, diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 9a667bb9ce..acf0c4f87e 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -46,6 +46,8 @@ const TEMPLATE_MAP: Record = { // service routes, added only to make typescript happy '/login': 'login', + '/api/metrics': 'node API prometheus metrics', + '/api/log': 'node API request log', '/api/media-type': 'node API media type', '/api/proxy': 'node API proxy', '/api/csrf': 'node API CSRF token', diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index fb4c48e7fe..f74a16fb51 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -46,6 +46,8 @@ export const PAGE_TYPE_DICT: Record = { // service routes, added only to make typescript happy '/login': 'Login', + '/api/metrics': 'Node API: Prometheus metrics', + '/api/log': 'Node API: Request log', '/api/media-type': 'Node API: Media type', '/api/proxy': 'Node API: Proxy', '/api/csrf': 'Node API: CSRF token', diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index 3015e472db..20c4b92970 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -15,7 +15,7 @@ export enum EventTypes { CONTRACT_VERIFICATION = 'Contract verification', QR_CODE = 'QR code', PAGE_WIDGET = 'Page widget', - TX_INTERPRETATION_INTERACTION = 'Transaction interpratetion interaction', + TX_INTERPRETATION_INTERACTION = 'Transaction interpretation interaction', EXPERIMENT_STARTED = 'Experiment started', FILTERS = 'Filters', BUTTON_CLICK = 'Button click', @@ -98,8 +98,12 @@ Type extends EventTypes.PAGE_WIDGET ? ( { 'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; } | { - 'Type': 'Favorite app' | 'More button'; + 'Type': 'Favorite app' | 'More button' | 'Security score' | 'Total contracts' | 'Verified contracts' | 'Analyzed contracts'; 'Info': string; + 'Source': 'Discovery view' | 'Security view' | 'App modal' | 'App page' | 'Security score popup'; + } | { + 'Type': 'Security score'; + 'Source': 'Analyzed contracts popup'; } ) : Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? { diff --git a/lib/monitoring/metrics.ts b/lib/monitoring/metrics.ts new file mode 100644 index 0000000000..db722561ce --- /dev/null +++ b/lib/monitoring/metrics.ts @@ -0,0 +1,20 @@ +import * as promClient from 'prom-client'; + +const metrics = (() => { + // eslint-disable-next-line no-restricted-properties + if (process.env.PROMETHEUS_METRICS_ENABLED !== 'true') { + return; + } + + promClient.register.clear(); + + const requestCounter = new promClient.Counter({ + name: 'request_counter', + help: 'Number of incoming requests', + labelNames: [ 'route', 'bot' ] as const, + }); + + return { requestCounter }; +})(); + +export default metrics; diff --git a/lib/socket/types.ts b/lib/socket/types.ts index 2bb2d47e71..b2d8d7301a 100644 --- a/lib/socket/types.ts +++ b/lib/socket/types.ts @@ -22,6 +22,7 @@ SocketMessage.AddressTokenBalance | SocketMessage.AddressTokenBalancesErc20 | SocketMessage.AddressTokenBalancesErc721 | SocketMessage.AddressTokenBalancesErc1155 | +SocketMessage.AddressTokenBalancesErc404 | SocketMessage.AddressCoinBalance | SocketMessage.AddressTxs | SocketMessage.AddressTxsPending | @@ -57,6 +58,7 @@ export namespace SocketMessage { export type AddressTokenBalancesErc20 = SocketMessageParamsGeneric<'updated_token_balances_erc_20', AddressTokensBalancesSocketMessage>; export type AddressTokenBalancesErc721 = SocketMessageParamsGeneric<'updated_token_balances_erc_721', AddressTokensBalancesSocketMessage>; export type AddressTokenBalancesErc1155 = SocketMessageParamsGeneric<'updated_token_balances_erc_1155', AddressTokensBalancesSocketMessage>; + export type AddressTokenBalancesErc404 = SocketMessageParamsGeneric<'updated_token_balances_erc_404', AddressTokensBalancesSocketMessage>; export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', { coin_balance: AddressCoinBalanceHistoryItem }>; export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transactions: Array }>; export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transactions: Array }>; diff --git a/lib/token/tokenTypes.ts b/lib/token/tokenTypes.ts index 5246fc2418..4b7fabf9ea 100644 --- a/lib/token/tokenTypes.ts +++ b/lib/token/tokenTypes.ts @@ -3,6 +3,7 @@ import type { NFTTokenType, TokenType } from 'types/api/token'; export const NFT_TOKEN_TYPES: Array<{ title: string; id: NFTTokenType }> = [ { title: 'ERC-721', id: 'ERC-721' }, { title: 'ERC-1155', id: 'ERC-1155' }, + { title: 'ERC-404', id: 'ERC-404' }, ]; export const TOKEN_TYPES: Array<{ title: string; id: TokenType }> = [ diff --git a/lib/web3/client.ts b/lib/web3/client.ts index d89fcac808..8188f22869 100644 --- a/lib/web3/client.ts +++ b/lib/web3/client.ts @@ -3,7 +3,7 @@ import { createPublicClient, http } from 'viem'; import currentChain from './currentChain'; export const publicClient = (() => { - if (currentChain.rpcUrls.public.http.filter(Boolean).length === 0) { + if (currentChain.rpcUrls.default.http.filter(Boolean).length === 0) { return; } diff --git a/lib/web3/currentChain.ts b/lib/web3/currentChain.ts index 23a089e97b..dd3892859f 100644 --- a/lib/web3/currentChain.ts +++ b/lib/web3/currentChain.ts @@ -1,20 +1,16 @@ -import type { Chain } from 'wagmi'; +import { type Chain } from 'viem'; import config from 'configs/app'; -const currentChain: Chain = { +const currentChain = { id: Number(config.chain.id), name: config.chain.name ?? '', - network: config.chain.name ?? '', nativeCurrency: { decimals: config.chain.currency.decimals, name: config.chain.currency.name ?? '', symbol: config.chain.currency.symbol ?? '', }, rpcUrls: { - 'public': { - http: [ config.chain.rpcUrl ?? '' ], - }, 'default': { http: [ config.chain.rpcUrl ?? '' ], }, @@ -25,6 +21,7 @@ const currentChain: Chain = { url: config.app.baseUrl, }, }, -}; + testnet: config.chain.isTestnet, +} as const satisfies Chain; export default currentChain; diff --git a/lib/web3/useProvider.tsx b/lib/web3/useProvider.tsx index 029eb24835..43cc7aa8fa 100644 --- a/lib/web3/useProvider.tsx +++ b/lib/web3/useProvider.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import type { WindowProvider } from 'wagmi'; - -import 'wagmi/window'; import type { WalletType } from 'types/client/wallets'; +import type { WalletProvider } from 'types/web3'; import config from 'configs/app'; const feature = config.features.web3Wallet; export default function useProvider() { - const [ provider, setProvider ] = React.useState(); + const [ provider, setProvider ] = React.useState(); const [ wallet, setWallet ] = React.useState(); const initializeProvider = React.useMemo(() => async() => { diff --git a/lib/web3/wagmiConfig.ts b/lib/web3/wagmiConfig.ts new file mode 100644 index 0000000000..f90d0a069f --- /dev/null +++ b/lib/web3/wagmiConfig.ts @@ -0,0 +1,38 @@ +import { defaultWagmiConfig } from '@web3modal/wagmi/react/config'; +import { http } from 'viem'; +import type { CreateConfigParameters } from 'wagmi'; + +import config from 'configs/app'; +import currentChain from 'lib/web3/currentChain'; +const feature = config.features.blockchainInteraction; + +const wagmiConfig = (() => { + try { + if (!feature.isEnabled) { + throw new Error(); + } + + const chains: CreateConfigParameters['chains'] = [ currentChain ]; + + const wagmiConfig = defaultWagmiConfig({ + chains, + multiInjectedProviderDiscovery: true, + transports: { + [currentChain.id]: http(), + }, + projectId: feature.walletConnect.projectId, + metadata: { + name: `${ config.chain.name } explorer`, + description: `${ config.chain.name } explorer`, + url: config.app.baseUrl, + icons: [ config.UI.sidebar.icon.default ].filter(Boolean), + }, + enableEmail: true, + ssr: true, + }); + + return wagmiConfig; + } catch (error) {} +})(); + +export default wagmiConfig; diff --git a/mocks/ad/textAd.ts b/mocks/ad/textAd.ts index 6ee891bea8..0e5bb6feae 100644 --- a/mocks/ad/textAd.ts +++ b/mocks/ad/textAd.ts @@ -2,7 +2,7 @@ export const duck = { ad: { name: 'Hello utia!', description_short: 'Utia is the best! Go with utia! Utia is the best! Go with utia!', - thumbnail: 'https://utia.utia', + thumbnail: 'http://localhost:3100/utia.jpg', url: 'https://test.url', cta_button: 'Click me!', }, diff --git a/mocks/address/tokens.ts b/mocks/address/tokens.ts index 9378525817..f3fd58b8d5 100644 --- a/mocks/address/tokens.ts +++ b/mocks/address/tokens.ts @@ -105,6 +105,20 @@ export const erc1155LongId: AddressTokenBalance = { value: '42', }; +export const erc404a: AddressTokenBalance = { + token: tokens.tokenInfoERC404, + token_id: '42', + token_instance: tokenInstance.base, + value: '240000000000000', +}; + +export const erc404b: AddressTokenBalance = { + token: tokens.tokenInfoERC404, + token_instance: null, + value: '11', + token_id: null, +}; + export const erc20List = { items: [ erc20a, @@ -129,6 +143,13 @@ export const erc1155List = { ], }; +export const erc404List = { + items: [ + erc404a, + erc404b, + ], +}; + export const nfts: AddressNFTsResponse = { items: [ { @@ -143,6 +164,12 @@ export const nfts: AddressNFTsResponse = { token_type: 'ERC-721', value: '1', }, + { + ...tokenInstance.unique, + token: tokens.tokenInfoERC404, + token_type: 'ERC-404', + value: '11000', + }, ], next_page_params: null, }; diff --git a/mocks/apps/securityReports.ts b/mocks/apps/securityReports.ts new file mode 100644 index 0000000000..824a6fbe13 --- /dev/null +++ b/mocks/apps/securityReports.ts @@ -0,0 +1,58 @@ +export const securityReports = [ + { + appName: 'token-approval-tracker', + doc: 'http://docs.li.fi/smart-contracts/deployments#mainnet', + chainsData: { + '1': { + overallInfo: { + verifiedNumber: 1, + totalContractsNumber: 1, + solidityScanContractsNumber: 1, + securityScore: 87.5, + issueSeverityDistribution: { + critical: 4, + gas: 1, + high: 0, + informational: 4, + low: 2, + medium: 0, + }, + }, + contractsData: [ + { + address: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + isVerified: true, + solidityScanReport: { + connection_id: '', + contract_address: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + contract_chain: 'optimism', + contract_platform: 'blockscout', + contract_url: 'http://optimism.blockscout.com/address/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + contractname: 'LiFiDiamond', + is_quick_scan: true, + node_reference_id: null, + request_type: 'threat_scan', + scanner_reference_url: 'http://solidityscan.com/quickscan/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE/blockscout/eth?ref=blockscout', + scan_status: 'scan_done', + scan_summary: { + issue_severity_distribution: { + critical: 0, + gas: 1, + high: 0, + informational: 4, + low: 2, + medium: 0, + }, + lines_analyzed_count: 72, + scan_time_taken: 1, + score: '4.38', + score_v2: '87.50', + threat_score: '100.00', + }, + }, + }, + ], + }, + }, + }, +]; diff --git a/mocks/noves/transaction.ts b/mocks/noves/transaction.ts new file mode 100644 index 0000000000..6feb72a564 --- /dev/null +++ b/mocks/noves/transaction.ts @@ -0,0 +1,103 @@ +import type { NovesResponseData } from 'types/api/noves'; + +import type { TokensData } from 'ui/tx/assetFlows/utils/getTokensData'; + +export const hash = '0x380400d04ebb4179a35b1d7fdef260776915f015e978f8587ef2704b843d4e53'; + +export const transaction: NovesResponseData = { + accountAddress: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + chain: 'eth-goerli', + classificationData: { + description: 'Called function \'stake\' on contract 0xef326CdAdA59D3A740A76bB5f4F88Fb2.', + protocol: { + name: null, + }, + received: [], + sent: [ + { + action: 'sent', + actionFormatted: 'Sent', + amount: '3000', + from: { + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + name: 'This wallet', + }, + to: { + address: '0xdD15D2650387Fb6FEDE27ae7392C402a393F8A37', + name: null, + }, + token: { + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + decimals: 18, + name: 'PQR-Test', + symbol: 'PQR', + }, + }, + { + action: 'paidGas', + actionFormatted: 'Paid Gas', + amount: '0.000395521502109448', + from: { + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + name: 'This wallet', + }, + to: { + address: null, + name: 'Validators', + }, + token: { + address: 'ETH', + decimals: 18, + name: 'ETH', + symbol: 'ETH', + }, + }, + ], + source: { + type: null, + }, + type: 'unclassified', + typeFormatted: 'Unclassified', + }, + rawTransactionData: { + blockNumber: 10388918, + fromAddress: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + gas: 275079, + gasPrice: 1500000008, + timestamp: 1705488588, + toAddress: '0xef326CdAdA59D3A740A76bB5f4F88Fb2f1076164', + transactionFee: { + amount: '395521502109448', + token: { + decimals: 18, + symbol: 'ETH', + }, + }, + transactionHash: '0x380400d04ebb4179a35b1d7fdef260776915f015e978f8587ef2704b843d4e53', + }, + txTypeVersion: 2, +}; + +export const tokenData: TokensData = { + nameList: [ 'PQR-Test', 'ETH' ], + symbolList: [ 'PQR' ], + idList: [], + byName: { + 'PQR-Test': { + name: 'PQR-Test', + symbol: 'PQR', + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + id: undefined, + }, + ETH: { name: 'ETH', symbol: null, address: '', id: undefined }, + }, + bySymbol: { + PQR: { + name: 'PQR-Test', + symbol: 'PQR', + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + id: undefined, + }, + 'null': { name: 'ETH', symbol: null, address: '', id: undefined }, + }, +}; diff --git a/mocks/tokens/tokenInfo.ts b/mocks/tokens/tokenInfo.ts index 1034b9bc64..a732712775 100644 --- a/mocks/tokens/tokenInfo.ts +++ b/mocks/tokens/tokenInfo.ts @@ -28,7 +28,7 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = { symbol: 'HyFi', total_supply: '369000000000000000000000000', type: 'ERC-20', - icon_url: 'https://example.com/token-icon.png', + icon_url: 'http://localhost:3000/token-icon.png', }; export const tokenInfoERC20b: TokenInfo<'ERC-20'> = { @@ -174,6 +174,19 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = { icon_url: null, }; +export const tokenInfoERC404: TokenInfo<'ERC-404'> = { + address: '0xB5C457dDB4cE3312a6C5a2b056a1652bd542a208', + circulating_market_cap: '0.0', + decimals: '18', + exchange_rate: '1484.13', + holders: '81', + icon_url: null, + name: 'OMNI404', + symbol: 'O404', + total_supply: '6482275000000000000', + type: 'ERC-404', +}; + export const bridgedTokenA: TokenInfo<'ERC-20'> = { ...tokenInfoERC20a, is_bridged: true, diff --git a/mocks/tokens/tokenTransfer.ts b/mocks/tokens/tokenTransfer.ts index 1e0573d43e..e756d14617 100644 --- a/mocks/tokens/tokenTransfer.ts +++ b/mocks/tokens/tokenTransfer.ts @@ -170,6 +170,64 @@ export const erc1155D: TokenTransfer = { total: { token_id: '456', value: '42', decimals: null }, }; +export const erc404A: TokenTransfer = { + from: { + hash: '0x0000000000000000000000000000000000000000', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + to: { + hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', + }, + token: { + address: '0xF56b7693E4212C584de4a83117f805B8E89224CB', + circulating_market_cap: null, + decimals: null, + exchange_rate: null, + holders: '1', + name: null, + symbol: 'MY_SYMBOL_IS_VERY_LONG', + type: 'ERC-404', + total_supply: '0', + icon_url: null, + }, + total: { + value: '42000000000000000000000000', + decimals: '18', + token_id: null, + }, + tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', + type: 'token_transfer', + method: 'swap', + timestamp: '2022-10-10T14:34:30.000000Z', + block_hash: '1', + log_index: '1', +}; + +export const erc404B: TokenTransfer = { + ...erc404A, + token: { + ...erc404A.token, + name: 'SastanaNFT', + symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', + }, + total: { token_id: '4625304364899952' }, +}; + export const mixTokens: TokenTransferResponse = { items: [ erc20, @@ -178,6 +236,8 @@ export const mixTokens: TokenTransferResponse = { erc1155B, erc1155C, erc1155D, + erc404A, + erc404B, ], next_page_params: null, }; diff --git a/mocks/txs/tx.ts b/mocks/txs/tx.ts index 3940426433..0b97508282 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -127,6 +127,8 @@ export const withTokenTransfer: Transaction = { tokenTransferMock.erc1155B, tokenTransferMock.erc1155C, tokenTransferMock.erc1155D, + tokenTransferMock.erc404A, + tokenTransferMock.erc404B, ], token_transfers_overflow: true, tx_types: [ diff --git a/mocks/zkSync/zkSyncTxnBatch.ts b/mocks/zkSync/zkSyncTxnBatch.ts new file mode 100644 index 0000000000..ab865dc513 --- /dev/null +++ b/mocks/zkSync/zkSyncTxnBatch.ts @@ -0,0 +1,20 @@ +import type { ZkSyncBatch } from 'types/api/zkSyncL2'; + +export const base: ZkSyncBatch = { + commit_transaction_hash: '0x7cd80c88977c2b310f79196b0b2136da18012be015ce80d0d9e9fe6cfad52b16', + commit_transaction_timestamp: '2022-03-19T09:37:38.726996Z', + end_block: 1245490, + execute_transaction_hash: '0x110b9a19afbabd5818a996ab2b493a9b23c888d73d95f1ab5272dbae503e103a', + execute_transaction_timestamp: '2022-03-19T10:29:05.358066Z', + l1_gas_price: '4173068062', + l1_tx_count: 0, + l2_fair_gas_price: '100000000', + l2_tx_count: 287, + number: 8051, + prove_transaction_hash: '0xb424162ba5afe17c710dceb5fc8d15d7d46a66223454dae8c74aa39f6802625b', + prove_transaction_timestamp: '2022-03-19T10:29:05.279179Z', + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', + start_block: 1245209, + status: 'Executed on L1', + timestamp: '2022-03-19T09:05:49.000000Z', +}; diff --git a/mocks/zkSync/zkSyncTxnBatches.ts b/mocks/zkSync/zkSyncTxnBatches.ts new file mode 100644 index 0000000000..a717308641 --- /dev/null +++ b/mocks/zkSync/zkSyncTxnBatches.ts @@ -0,0 +1,49 @@ +import type { ZkSyncBatchesItem, ZkSyncBatchesResponse } from 'types/api/zkSyncL2'; + +export const sealed: ZkSyncBatchesItem = { + commit_transaction_hash: null, + commit_transaction_timestamp: null, + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8055, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sealed on L2', + timestamp: '2022-03-19T12:53:36.000000Z', + tx_count: 738, +}; + +export const sent: ZkSyncBatchesItem = { + commit_transaction_hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', + commit_transaction_timestamp: '2022-03-19T13:09:07.357570Z', + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8054, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sent to L1', + timestamp: '2022-03-19T11:36:45.000000Z', + tx_count: 766, +}; + +export const executed: ZkSyncBatchesItem = { + commit_transaction_hash: '0xa2628f477e1027ac1c60fa75c186b914647769ac1cb9c7e1cab50b13506a0035', + commit_transaction_timestamp: '2022-03-19T11:52:18.963659Z', + execute_transaction_hash: '0xb7bd6b2b17498c66d3f6e31ac3685133a81b7f728d4f6a6f42741daa257d0d68', + execute_transaction_timestamp: '2022-03-19T13:28:16.712656Z', + number: 8053, + prove_transaction_hash: '0x9d44f2b775bd771f8a53205755b3897929aa672d2cd419b3b988c16d41d4f21e', + prove_transaction_timestamp: '2022-03-19T13:28:16.603104Z', + status: 'Executed on L1', + timestamp: '2022-03-19T10:01:52.000000Z', + tx_count: 1071, +}; + +export const baseResponse: ZkSyncBatchesResponse = { + items: [ + sealed, + sent, + executed, + ], + next_page_params: null, +}; diff --git a/nextjs/PageNextJs.tsx b/nextjs/PageNextJs.tsx index 1c15c9f419..8b3c6e7611 100644 --- a/nextjs/PageNextJs.tsx +++ b/nextjs/PageNextJs.tsx @@ -3,6 +3,7 @@ import React from 'react'; import type { Route } from 'nextjs-routes'; +import config from 'configs/app'; import useAdblockDetect from 'lib/hooks/useAdblockDetect'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import * as metadata from 'lib/metadata'; @@ -34,9 +35,14 @@ const PageNextJs = (props: Props) => { { opengraph.description && } + + + { /* Twitter Meta Tags */ } + + + { opengraph.description && } - { props.children } diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index 413b19bf29..6b3d959ab2 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -1,7 +1,9 @@ import type { GetServerSideProps } from 'next'; import config from 'configs/app'; +import isNeedProxy from 'lib/api/isNeedProxy'; const rollupFeature = config.features.rollup; +const adBannerFeature = config.features.adsBanner; export type Props = { cookies: string; @@ -12,9 +14,23 @@ export type Props = { number: string; q: string; name: string; + adBannerProvider: string; } export const base: GetServerSideProps = async({ req, query }) => { + const adBannerProvider = (() => { + if (adBannerFeature.isEnabled) { + if ('additionalProvider' in adBannerFeature && adBannerFeature.additionalProvider) { + // we need to get a random ad provider on the server side to keep it consistent with the client side + const randomIndex = Math.round(Math.random()); + return [ adBannerFeature.provider, adBannerFeature.additionalProvider ][randomIndex]; + } else { + return adBannerFeature.provider; + } + } + return ''; + })(); + return { props: { cookies: req.headers.cookie || '', @@ -25,6 +41,7 @@ export const base: GetServerSideProps = async({ req, query }) => { number: query.number?.toString() || '', q: query.q?.toString() || '', name: query.name?.toString() || '', + adBannerProvider, }, }; }; @@ -92,8 +109,8 @@ export const optimisticRollup: GetServerSideProps = async(context) => { return base(context); }; -export const zkEvmRollup: GetServerSideProps = async(context) => { - if (!(rollupFeature.isEnabled && rollupFeature.type === 'zkEvm')) { +export const batch: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync'))) { return { notFound: true, }; @@ -201,3 +218,24 @@ export const gasTracker: GetServerSideProps = async(context) => { return base(context); }; + +export const dataAvailability: GetServerSideProps = async(context) => { + if (!config.features.dataAvailability.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const login: GetServerSideProps = async(context) => { + + if (!isNeedProxy()) { + return { + notFound: true, + }; + } + + return base(context); +}; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index ee87eb5dc1..5f5a8edd27 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -18,7 +18,9 @@ declare module "nextjs-routes" { | DynamicRoute<"/address/[hash]", { "hash": string }> | StaticRoute<"/api/csrf"> | StaticRoute<"/api/healthz"> + | StaticRoute<"/api/log"> | StaticRoute<"/api/media-type"> + | StaticRoute<"/api/metrics"> | StaticRoute<"/api/proxy"> | StaticRoute<"/api-docs"> | DynamicRoute<"/apps/[id]", { "id": string }> diff --git a/package.json b/package.json index 3999ed8b9e..3c85287b18 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,14 @@ "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.41.1-focal ./tools/scripts/pw.docker.sh", + "test:pw:docker:deps": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.deps.sh", "test:pw:ci": "yarn test:pw --project=$PW_PROJECT", "test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js", "test:jest": "jest", "test:jest:watch": "jest --watch", - "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh" + "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh", + "monitoring:prometheus:local": "docker run --name blockscout_prometheus -d -p 127.0.0.1:9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus", + "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise" }, "dependencies": { "@chakra-ui/react": "2.7.1", @@ -57,13 +60,13 @@ "@tanstack/react-query-devtools": "^5.4.3", "@types/papaparse": "^5.3.5", "@types/react-scroll": "^1.8.4", - "@web3modal/wagmi": "3.5.0", + "@web3modal/wagmi": "4.1.3", "bignumber.js": "^9.1.0", "blo": "^1.1.1", "chakra-react-select": "^4.4.3", "crypto-js": "^4.2.0", "d3": "^7.6.1", - "dappscout-iframe": "0.2.0", + "dappscout-iframe": "0.2.1", "dayjs": "^1.11.5", "dom-to-image": "^2.6.0", "focus-visible": "^5.2.0", @@ -86,6 +89,7 @@ "phoenix": "^1.6.15", "pino-http": "^8.2.1", "pino-pretty": "^9.1.1", + "prom-client": "15.1.1", "qrcode": "^1.5.1", "react": "18.2.0", "react-device-detect": "^2.2.3", @@ -99,8 +103,8 @@ "react-scroll": "^1.8.7", "swagger-ui-react": "^5.9.0", "use-font-face-observer": "^1.2.1", - "viem": "1.20.1", - "wagmi": "1.4.12", + "viem": "2.9.6", + "wagmi": "2.5.16", "xss": "^1.0.14" }, "devDependencies": { @@ -148,7 +152,7 @@ "svgo": "^2.8.0", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", - "typescript": "^5.1.0", + "typescript": "5.4.2", "vite-plugin-svgr": "^2.2.2", "vite-tsconfig-paths": "^3.5.2", "ws": "^8.11.0" diff --git a/pages/_document.tsx b/pages/_document.tsx index 74ebe08fb5..2101c04c4f 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -5,6 +5,7 @@ import React from 'react'; import * as serverTiming from 'nextjs/utils/serverTiming'; +import getApiDataForSocialPreview from 'lib/metadata/getApiDataForSocialPreview'; import theme from 'theme'; import * as svgSprite from 'ui/shared/IconSvg'; @@ -21,6 +22,8 @@ class MyDocument extends Document { return result; }; + await getApiDataForSocialPreview(ctx.req, ctx.res, ctx.pathname); + const initialProps = await Document.getInitialProps(ctx); return initialProps; diff --git a/pages/api/log.ts b/pages/api/log.ts new file mode 100644 index 0000000000..cb89b35f59 --- /dev/null +++ b/pages/api/log.ts @@ -0,0 +1,9 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +import { httpLogger } from 'nextjs/utils/logger'; + +export default async function logHandler(req: NextApiRequest, res: NextApiResponse) { + httpLogger(req, res); + + res.status(200).send('ok'); +} diff --git a/pages/api/metrics.ts b/pages/api/metrics.ts new file mode 100644 index 0000000000..d2f00ff430 --- /dev/null +++ b/pages/api/metrics.ts @@ -0,0 +1,13 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import * as promClient from 'prom-client'; + +// eslint-disable-next-line no-restricted-properties +const isEnabled = process.env.PROMETHEUS_METRICS_ENABLED === 'true'; + +isEnabled && promClient.collectDefaultMetrics(); + +export default async function metricsHandler(req: NextApiRequest, res: NextApiResponse) { + const metrics = await promClient.register.metrics(); + res.setHeader('Content-type', promClient.register.contentType); + res.send(metrics); +} diff --git a/pages/batches/[number].tsx b/pages/batches/[number].tsx index 68e4356a78..870a1cc7b4 100644 --- a/pages/batches/[number].tsx +++ b/pages/batches/[number].tsx @@ -5,16 +5,32 @@ import React from 'react'; import type { Props } from 'nextjs/getServerSideProps'; import PageNextJs from 'nextjs/PageNextJs'; -const ZkEvmL2TxnBatch = dynamic(() => import('ui/pages/ZkEvmL2TxnBatch'), { ssr: false }); +import config from 'configs/app'; + +const rollupFeature = config.features.rollup; + +const Batch = dynamic(() => { + if (!rollupFeature.isEnabled) { + throw new Error('Rollup feature is not enabled.'); + } + + switch (rollupFeature.type) { + case 'zkEvm': + return import('ui/pages/ZkEvmL2TxnBatch'); + case 'zkSync': + return import('ui/pages/ZkSyncL2TxnBatch'); + } + throw new Error('Txn batches feature is not enabled.'); +}, { ssr: false }); const Page: NextPage = (props: Props) => { return ( - + ); }; export default Page; -export { zkEvmRollup as getServerSideProps } from 'nextjs/getServerSideProps'; +export { batch as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/batches/index.tsx b/pages/batches/index.tsx index e14ff7ca49..f5bea98b4b 100644 --- a/pages/batches/index.tsx +++ b/pages/batches/index.tsx @@ -15,6 +15,8 @@ const Batches = dynamic(() => { switch (rollupFeature.type) { case 'zkEvm': return import('ui/pages/ZkEvmL2TxnBatches'); + case 'zkSync': + return import('ui/pages/ZkSyncL2TxnBatches'); case 'optimistic': return import('ui/pages/OptimisticL2TxnBatches'); } diff --git a/pages/blobs/[hash].tsx b/pages/blobs/[hash].tsx index f746e4a82c..f1056d36fa 100644 --- a/pages/blobs/[hash].tsx +++ b/pages/blobs/[hash].tsx @@ -17,4 +17,4 @@ const Page: NextPage = (props: Props) => { export default Page; -export { base as getServerSideProps } from 'nextjs/getServerSideProps'; +export { dataAvailability as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/login.tsx b/pages/login.tsx index a74410f613..11562f856b 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -15,4 +15,4 @@ const Page: NextPage = () => { export default Page; -export { base as getServerSideProps } from 'nextjs/getServerSideProps'; +export { login as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts index eeb35835bf..d6c7c6c231 100644 --- a/playwright-ct.config.ts +++ b/playwright-ct.config.ts @@ -4,6 +4,8 @@ import react from '@vitejs/plugin-react'; import svgr from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; +import appConfig from 'configs/app'; + /** * See https://playwright.dev/docs/test-configuration. */ @@ -36,6 +38,7 @@ const config: PlaywrightTestConfig = defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { + baseURL: appConfig.app.baseUrl, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -60,17 +63,26 @@ const config: PlaywrightTestConfig = defineConfig({ minify: false, }, resolve: { - alias: { + alias: [ // There is an issue with building these package using vite that I cannot resolve // The solution described here - https://github.com/vitejs/vite/issues/9703#issuecomment-1216662109 // doesn't seam to work well with our setup // so for now we just mock these modules in tests - '@metamask/post-message-stream': './playwright/mocks/modules/@metamask/post-message-stream.js', - '@metamask/providers': './playwright/mocks/modules/@metamask/providers.js', + { find: '@metamask/post-message-stream', replacement: './playwright/mocks/modules/@metamask/post-message-stream.js' }, + { find: '@metamask/providers', replacement: './playwright/mocks/modules/@metamask/providers.js' }, + + // '@metamask/sdk imports the browser module as UMD, but @wagmi/connectors expects it to be ESM + // so we do a little remapping here + { find: '@metamask/sdk', replacement: './node_modules/@metamask/sdk/dist/browser/es/metamask-sdk.js' }, // Mock for growthbook to test feature flags - 'lib/growthbook/useFeatureValue': './playwright/mocks/lib/growthbook/useFeatureValue.js', - }, + { find: 'lib/growthbook/useFeatureValue', replacement: './playwright/mocks/lib/growthbook/useFeatureValue.js' }, + + // The createWeb3Modal() function from web3modal/wagmi/react somehow pollutes the global styles which causes the tests to fail + // We don't call this function in TestApp and since we use useWeb3Modal() and useWeb3ModalState() hooks in the code, we have to mock the module + // Otherwise it will complain that createWeb3Modal() is no called before the hooks are used + { find: /^@web3modal\/wagmi\/react$/, replacement: './playwright/mocks/modules/@web3modal/wagmi/react.js' }, + ], }, define: { 'process.env': '__envs', // Port process.env over window.__envs diff --git a/playwright/TestApp.tsx b/playwright/TestApp.tsx index b4b246a621..8db3aee3b2 100644 --- a/playwright/TestApp.tsx +++ b/playwright/TestApp.tsx @@ -1,19 +1,18 @@ import { ChakraProvider } from '@chakra-ui/react'; import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'; import React from 'react'; -import { WagmiConfig } from 'wagmi'; -import { mainnet } from 'wagmi/chains'; +import { WagmiProvider } from 'wagmi'; import type { Props as PageProps } from 'nextjs/getServerSideProps'; import { AppContextProvider } from 'lib/contexts/app'; import { SocketProvider } from 'lib/socket/context'; +import wagmiConfig from 'lib/web3/wagmiConfig'; import * as app from 'playwright/utils/app'; import theme from 'theme'; -type Props = { +export type Props = { children: React.ReactNode; withSocket?: boolean; appContext?: { @@ -31,25 +30,10 @@ const defaultAppContext = { number: '', q: '', name: '', + adBannerProvider: 'slise', }, }; -// >>> Web3 stuff -const chains = [ mainnet ]; -const WALLET_CONNECT_PROJECT_ID = 'PROJECT_ID'; - -const wagmiConfig = defaultWagmiConfig({ - chains, - projectId: WALLET_CONNECT_PROJECT_ID, -}); - -createWeb3Modal({ - wagmiConfig, - projectId: WALLET_CONNECT_PROJECT_ID, - chains, -}); -// <<<< - const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props) => { const [ queryClient ] = React.useState(() => new QueryClient({ defaultOptions: { @@ -66,9 +50,9 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props - + { children } - + diff --git a/playwright/fixtures/mockApiResponse.ts b/playwright/fixtures/mockApiResponse.ts new file mode 100644 index 0000000000..595b623ae9 --- /dev/null +++ b/playwright/fixtures/mockApiResponse.ts @@ -0,0 +1,26 @@ +import type { TestFixture, Page } from '@playwright/test'; + +import buildUrl from 'lib/api/buildUrl'; +import type { ResourceName, ResourcePayload } from 'lib/api/resources'; + +interface Options { + pathParams?: Parameters>[1]; + queryParams?: Parameters>[2]; +} + +export type MockApiResponseFixture = (resourceName: R, responseMock: ResourcePayload, options?: Options) => Promise; + +const fixture: TestFixture = async({ page }, use) => { + await use(async(resourceName, responseMock, options) => { + const apiUrl = buildUrl(resourceName, options?.pathParams, options?.queryParams); + + await page.route(apiUrl, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(responseMock), + })); + + return apiUrl; + }); +}; + +export default fixture; diff --git a/playwright/fixtures/mockAssetResponse.ts b/playwright/fixtures/mockAssetResponse.ts new file mode 100644 index 0000000000..8da0b75fea --- /dev/null +++ b/playwright/fixtures/mockAssetResponse.ts @@ -0,0 +1,15 @@ +import type { TestFixture, Page } from '@playwright/test'; + +export type MockAssetResponseFixture = (url: string, path: string) => Promise; + +const fixture: TestFixture = async({ page }, use) => { + await use(async(url, path) => { + + await page.route(url, (route) => route.fulfill({ + status: 200, + path, + })); + }); +}; + +export default fixture; diff --git a/playwright/fixtures/render.tsx b/playwright/fixtures/render.tsx new file mode 100644 index 0000000000..88f7fe669f --- /dev/null +++ b/playwright/fixtures/render.tsx @@ -0,0 +1,35 @@ +import type { MountOptions } from '@playwright/experimental-ct-react'; +import type { Locator, TestFixture } from '@playwright/test'; +import type router from 'next/router'; +import React from 'react'; + +import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; + +import type { Props as TestAppProps } from 'playwright/TestApp'; +import TestApp from 'playwright/TestApp'; + +interface MountResult extends Locator { + unmount(): Promise; + update(component: JSX.Element): Promise; +} + +type Mount = (component: JSX.Element, options?: MountOptions) => Promise; + +interface Options extends JsonObject { + hooksConfig?: { + router: Partial>; + }; +} + +export type RenderFixture = (component: JSX.Element, options?: Options, props?: Omit) => Promise + +const fixture: TestFixture = async({ mount }, use) => { + await use((component, options, props) => { + return mount( + { component }, + options, + ); + }); +}; + +export default fixture; diff --git a/playwright/fixtures/socketServer.ts b/playwright/fixtures/socketServer.ts index 43c3726371..f630cdd012 100644 --- a/playwright/fixtures/socketServer.ts +++ b/playwright/fixtures/socketServer.ts @@ -10,16 +10,16 @@ import type { Transaction } from 'types/api/transaction'; import * as app from 'playwright/utils/app'; -type ReturnType = () => Promise; +export type CreateSocketFixture = () => Promise; type Channel = [string, string, string]; export interface SocketServerFixture { - createSocket: ReturnType; + createSocket: CreateSocketFixture; } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export const createSocket: TestFixture = async({ page }, use) => { +export const createSocket: TestFixture = async({ page }, use) => { const socketServer = new WebSocketServer({ port: app.socketPort }); const connectionPromise = new Promise((resolve) => { @@ -62,6 +62,7 @@ export function sendMessage(socket: WebSocket, channel: Channel, msg: 'token_bal export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_20', payload: AddressTokensBalancesSocketMessage): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_721', payload: AddressTokensBalancesSocketMessage): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_1155', payload: AddressTokensBalancesSocketMessage): void; +export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_404', payload: AddressTokensBalancesSocketMessage): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transaction: number }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transactions: Array }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'pending_transaction', payload: { pending_transaction: number }): void; diff --git a/playwright/lib.tsx b/playwright/lib.tsx new file mode 100644 index 0000000000..215f71d17e --- /dev/null +++ b/playwright/lib.tsx @@ -0,0 +1,64 @@ +/* eslint-disable no-console */ +import { test as base } from '@playwright/experimental-ct-react'; + +import * as textAdMock from 'mocks/ad/textAd'; + +import type { MockApiResponseFixture } from './fixtures/mockApiResponse'; +import mockApiResponseFixture from './fixtures/mockApiResponse'; +import type { MockAssetResponseFixture } from './fixtures/mockAssetResponse'; +import mockAssetResponseFixture from './fixtures/mockAssetResponse'; +import type { RenderFixture } from './fixtures/render'; +import renderFixture from './fixtures/render'; +import type { CreateSocketFixture } from './fixtures/socketServer'; +import { createSocket as createSocketFixture } from './fixtures/socketServer'; + +interface Fixtures { + render: RenderFixture; + mockApiResponse: MockApiResponseFixture; + mockAssetResponse: MockAssetResponseFixture; + createSocket: CreateSocketFixture; +} + +const test = base.extend({ + render: renderFixture, + mockApiResponse: mockApiResponseFixture, + mockAssetResponse: mockAssetResponseFixture, + createSocket: createSocketFixture, +}); + +test.beforeEach(async({ page }) => { + // debug + const isDebug = process.env.PWDEBUG === '1'; + + if (isDebug) { + page.on('console', msg => console.log(msg.text())); + page.on('request', request => console.info('\x1b[34m%s\x1b[0m', '>>', request.method(), request.url())); + page.on('response', response => console.info('\x1b[35m%s\x1b[0m', '<<', String(response.status()), response.url())); + } + + // Abort all other requests to external resources + await page.route('**', (route) => { + if (!route.request().url().startsWith('http://localhost')) { + isDebug && console.info('Aborting request to', route.request().url()); + route.abort(); + } else { + route.continue(); + } + }); + + // with few exceptions: + // 1. mock text AD requests + await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ + status: 200, + body: JSON.stringify(textAdMock.duck), + })); + await page.route(textAdMock.duck.ad.thumbnail, (route) => { + return route.fulfill({ + status: 200, + path: './playwright/mocks/image_s.jpg', + }); + }); +}); + +export * from '@playwright/experimental-ct-react'; +export { test }; diff --git a/playwright/mocks/modules/@web3modal/wagmi/react.js b/playwright/mocks/modules/@web3modal/wagmi/react.js new file mode 100644 index 0000000000..7765272c71 --- /dev/null +++ b/playwright/mocks/modules/@web3modal/wagmi/react.js @@ -0,0 +1,26 @@ +function useWeb3Modal() { + return { + open: () => {}, + }; +} + +function useWeb3ModalState() { + return { + isOpen: false, + }; +} + +function useWeb3ModalTheme() { + return { + setThemeMode: () => {}, + }; +} + +function createWeb3Modal() {} + +export { + createWeb3Modal, + useWeb3Modal, + useWeb3ModalState, + useWeb3ModalTheme, +}; diff --git a/playwright/utils/buildApiUrl.ts b/playwright/utils/buildApiUrl.ts index 4279efb71d..f8ce6bc063 100644 --- a/playwright/utils/buildApiUrl.ts +++ b/playwright/utils/buildApiUrl.ts @@ -1,11 +1,22 @@ import { compile } from 'path-to-regexp'; +import config from 'configs/app'; import type { ResourceName, ResourcePathParams } from 'lib/api/resources'; import { RESOURCES } from 'lib/api/resources'; +// DEPRECATED + +/** + * @deprecated please use fixture mockApiResponse from playwright/lib.tsx for rendering test suite + * + * @export + * @template R + * @param {R} resourceName + * @param {ResourcePathParams} [pathParams] + * @return {*} string + */ export default function buildApiUrl(resourceName: R, pathParams?: ResourcePathParams) { const resource = RESOURCES[resourceName]; - const defaultApi = 'https://' + process.env.NEXT_PUBLIC_API_HOST + ':' + process.env.NEXT_PUBLIC_API_PORT; - const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : defaultApi; + const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : config.api.endpoint; return origin + compile(resource.path)(pathParams); } diff --git a/playwright/utils/configs.ts b/playwright/utils/configs.ts index c167a76fed..d0a7ac6da9 100644 --- a/playwright/utils/configs.ts +++ b/playwright/utils/configs.ts @@ -41,6 +41,10 @@ export const featureEnvs = { { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'zkEvm' }, { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' }, ], + zkSyncRollup: [ + { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'zkSync' }, + { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' }, + ], userOps: [ { name: 'NEXT_PUBLIC_HAS_USER_OPS', value: 'true' }, ], diff --git a/prometheus.yml b/prometheus.yml new file mode 100644 index 0000000000..89b95e0ecb --- /dev/null +++ b/prometheus.yml @@ -0,0 +1,16 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + # - "first.rules" + # - "second.rules" + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ['localhost:9090'] + - job_name: frontend + metrics_path: /node-api/metrics + static_configs: + - targets: ['host.docker.internal:3000'] \ No newline at end of file diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index 57cdf88bae..82909a5a3b 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -4,6 +4,8 @@ | "ABI_slim" | "ABI" | "API" + | "apps_list" + | "apps_xs" | "apps" | "arrows/down-right" | "arrows/east-mini" @@ -11,6 +13,8 @@ | "arrows/north-east" | "arrows/south-east" | "arrows/up-down" + | "beta_xs" + | "beta" | "blob" | "blobs/image" | "blobs/raw" @@ -18,6 +22,7 @@ | "block_slim" | "block" | "brands/safe" + | "brands/solidity_scan" | "burger" | "check" | "clock-light" @@ -26,6 +31,8 @@ | "collection" | "contract_verified" | "contract" + | "contracts_verified" + | "contracts" | "copy" | "cross" | "delete" @@ -55,6 +62,7 @@ | "flame" | "gas_xl" | "gas" + | "gear_slim" | "gear" | "globe-b" | "globe" @@ -111,8 +119,8 @@ | "social/stats" | "social/telega" | "social/telegram_filled" - | "social/tweet" | "social/twitter_filled" + | "social/twitter" | "star_filled" | "star_outline" | "stats" @@ -134,6 +142,7 @@ | "txn_batches" | "unfinalized" | "uniswap" + | "up" | "user_op_slim" | "user_op" | "validator" diff --git a/public/static/identicon_logos/blockies.png b/public/static/identicon_logos/blockies.png new file mode 100644 index 0000000000..d9ccead353 Binary files /dev/null and b/public/static/identicon_logos/blockies.png differ diff --git a/public/static/identicon_logos/github.png b/public/static/identicon_logos/github.png new file mode 100644 index 0000000000..346f9d212e Binary files /dev/null and b/public/static/identicon_logos/github.png differ diff --git a/public/static/identicon_logos/gradient_avatar.png b/public/static/identicon_logos/gradient_avatar.png new file mode 100644 index 0000000000..396ee27848 Binary files /dev/null and b/public/static/identicon_logos/gradient_avatar.png differ diff --git a/public/static/identicon_logos/jazzicon.png b/public/static/identicon_logos/jazzicon.png new file mode 100644 index 0000000000..c06bc880ca Binary files /dev/null and b/public/static/identicon_logos/jazzicon.png differ diff --git a/stubs/RPC.ts b/stubs/RPC.ts index 333f33e605..0da0947c7c 100644 --- a/stubs/RPC.ts +++ b/stubs/RPC.ts @@ -84,6 +84,8 @@ export const GET_BLOCK: GetBlockReturnType = { withdrawals: Array(10).fill(WITHDRAWAL), withdrawalsRoot: TX_HASH, sealFields: [ '0x00' ], + blobGasUsed: BigInt(0), + excessBlobGas: BigInt(0), }; export const GET_BLOCK_WITH_TRANSACTIONS: GetBlockReturnType = { diff --git a/stubs/account.ts b/stubs/account.ts index cdb1bab556..92bd0861ce 100644 --- a/stubs/account.ts +++ b/stubs/account.ts @@ -48,6 +48,10 @@ export const WATCH_LIST_ITEM_WITH_TOKEN_INFO: WatchlistAddress = { incoming: true, outcoming: true, }, + 'ERC-404': { + incoming: true, + outcoming: true, + }, 'native': { incoming: true, outcoming: true, diff --git a/stubs/address.ts b/stubs/address.ts index 19f2a398ad..983565ca24 100644 --- a/stubs/address.ts +++ b/stubs/address.ts @@ -10,7 +10,7 @@ import type { import type { AddressesItem } from 'types/api/addresses'; import { ADDRESS_HASH } from './addressParams'; -import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INSTANCE } from './token'; +import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INFO_ERC_404, TOKEN_INSTANCE } from './token'; import { TX_HASH } from './tx'; export const ADDRESS_INFO: Address = { @@ -104,6 +104,13 @@ export const ADDRESS_NFT_1155: AddressNFT = { ...TOKEN_INSTANCE, }; +export const ADDRESS_NFT_404: AddressNFT = { + token_type: 'ERC-404', + token: TOKEN_INFO_ERC_404, + value: '10', + ...TOKEN_INSTANCE, +}; + export const ADDRESS_COLLECTION: AddressCollection = { token: TOKEN_INFO_ERC_1155, amount: '4', diff --git a/stubs/contract.ts b/stubs/contract.ts index 814786936f..d242609f97 100644 --- a/stubs/contract.ts +++ b/stubs/contract.ts @@ -65,6 +65,7 @@ export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = { export const SOLIDITYSCAN_REPORT: SolidityscanReport = { scan_report: { + contractname: 'BullRunners', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { diff --git a/stubs/noves/NovesTranslate.ts b/stubs/noves/NovesTranslate.ts new file mode 100644 index 0000000000..848ed6dab9 --- /dev/null +++ b/stubs/noves/NovesTranslate.ts @@ -0,0 +1,43 @@ +import type { NovesResponseData, NovesClassificationData, NovesRawTransactionData } from 'types/api/noves'; + +const NOVES_TRANSLATE_CLASSIFIED: NovesClassificationData = { + description: 'Sent 0.04 ETH', + received: [ { + action: 'Sent Token', + actionFormatted: 'Sent Token', + amount: '45', + from: { name: '', address: '0xa0393A76b132526a70450273CafeceB45eea6dEE' }, + to: { name: '', address: '0xa0393A76b132526a70450273CafeceB45eea6dEE' }, + token: { + address: '', + name: 'ETH', + symbol: 'ETH', + decimals: 18, + }, + } ], + sent: [], + source: { + type: '', + }, + type: '0x2', + typeFormatted: 'Send NFT', +}; + +const NOVES_TRANSLATE_RAW: NovesRawTransactionData = { + blockNumber: 1, + fromAddress: '0xCFC123a23dfeD71bDAE054e487989d863C525C73', + gas: 2, + gasPrice: 3, + timestamp: 20000, + toAddress: '0xCFC123a23dfeD71bDAE054e487989d863C525C73', + transactionFee: 2, + transactionHash: '0x128b79937a0eDE33258992c9668455f997f1aF24', +}; + +export const NOVES_TRANSLATE: NovesResponseData = { + accountAddress: '0x2b824349b320cfa72f292ab26bf525adb00083ba9fa097141896c3c8c74567cc', + chain: 'base', + txTypeVersion: 2, + rawTransactionData: NOVES_TRANSLATE_RAW, + classificationData: NOVES_TRANSLATE_CLASSIFIED, +}; diff --git a/stubs/token.ts b/stubs/token.ts index 23ab4519a6..1c90f3d16a 100644 --- a/stubs/token.ts +++ b/stubs/token.ts @@ -1,4 +1,13 @@ -import type { TokenCounters, TokenHolder, TokenInfo, TokenInstance, TokenType } from 'types/api/token'; +import type { + TokenCounters, + TokenHolder, + TokenHolders, + TokenHoldersPagination, + TokenInfo, + TokenInstance, + TokenType, +} from 'types/api/token'; +import type { TokenInstanceTransferPagination, TokenInstanceTransferResponse } from 'types/api/tokens'; import type { TokenTransfer, TokenTransferPagination, TokenTransferResponse } from 'types/api/tokenTransfer'; import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams'; @@ -31,6 +40,12 @@ export const TOKEN_INFO_ERC_1155: TokenInfo<'ERC-1155'> = { type: 'ERC-1155', }; +export const TOKEN_INFO_ERC_404: TokenInfo<'ERC-404'> = { + ...TOKEN_INFO_ERC_20, + circulating_market_cap: null, + type: 'ERC-404', +}; + export const TOKEN_COUNTERS: TokenCounters = { token_holders_count: '123456', transfers_count: '123456', @@ -47,6 +62,32 @@ export const TOKEN_HOLDER_ERC_1155: TokenHolder = { value: '1021378038331138520', }; +export const getTokenHoldersStub = (type?: TokenType, pagination: TokenHoldersPagination | null = null): TokenHolders => { + switch (type) { + case 'ERC-721': + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_20, 50, { next_page_params: pagination }); + case 'ERC-1155': + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_1155, 50, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_1155, 50, { next_page_params: pagination }); + default: + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_20, 50, { next_page_params: pagination }); + } +}; + +export const getTokenInstanceHoldersStub = (type?: TokenType, pagination: TokenHoldersPagination | null = null): TokenHolders => { + switch (type) { + case 'ERC-721': + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_20, 10, { next_page_params: pagination }); + case 'ERC-1155': + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_1155, 10, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_1155, 10, { next_page_params: pagination }); + default: + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_20, 10, { next_page_params: pagination }); + } +}; + export const TOKEN_TRANSFER_ERC_20: TokenTransfer = { block_hash: BLOCK_HASH, from: ADDRESS_PARAMS, @@ -81,17 +122,42 @@ export const TOKEN_TRANSFER_ERC_1155: TokenTransfer = { token: TOKEN_INFO_ERC_1155, }; +export const TOKEN_TRANSFER_ERC_404: TokenTransfer = { + ...TOKEN_TRANSFER_ERC_20, + total: { + token_id: '35870', + value: '123', + decimals: '18', + }, + token: TOKEN_INFO_ERC_404, +}; + export const getTokenTransfersStub = (type?: TokenType, pagination: TokenTransferPagination | null = null): TokenTransferResponse => { switch (type) { case 'ERC-721': return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_721, 50, { next_page_params: pagination }); case 'ERC-1155': return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_1155, 50, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_404, 50, { next_page_params: pagination }); default: return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_20, 50, { next_page_params: pagination }); } }; +export const getTokenInstanceTransfersStub = (type?: TokenType, pagination: TokenInstanceTransferPagination | null = null): TokenInstanceTransferResponse => { + switch (type) { + case 'ERC-721': + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_721, 10, { next_page_params: pagination }); + case 'ERC-1155': + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_1155, 10, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_404, 10, { next_page_params: pagination }); + default: + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_20, 10, { next_page_params: pagination }); + } +}; + export const TOKEN_INSTANCE: TokenInstance = { animation_url: null, external_app_url: 'https://vipsland.com/nft/collections/genesis/188882', diff --git a/stubs/zkSyncL2.ts b/stubs/zkSyncL2.ts new file mode 100644 index 0000000000..9d3782abcf --- /dev/null +++ b/stubs/zkSyncL2.ts @@ -0,0 +1,27 @@ +import type { ZkSyncBatch, ZkSyncBatchesItem } from 'types/api/zkSyncL2'; + +import { TX_HASH } from './tx'; + +export const ZKSYNC_L2_TXN_BATCHES_ITEM: ZkSyncBatchesItem = { + commit_transaction_hash: TX_HASH, + commit_transaction_timestamp: '2022-03-17T19:33:04.519145Z', + execute_transaction_hash: TX_HASH, + execute_transaction_timestamp: '2022-03-17T20:49:48.856345Z', + number: 8002, + prove_transaction_hash: TX_HASH, + prove_transaction_timestamp: '2022-03-17T20:49:48.772442Z', + status: 'Executed on L1', + timestamp: '2022-03-17T17:00:11.000000Z', + tx_count: 1215, +}; + +export const ZKSYNC_L2_TXN_BATCH: ZkSyncBatch = { + ...ZKSYNC_L2_TXN_BATCHES_ITEM, + start_block: 1245209, + end_block: 1245490, + l1_gas_price: '4173068062', + l1_tx_count: 0, + l2_fair_gas_price: '100000000', + l2_tx_count: 287, + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', +}; diff --git a/theme/components/Menu.ts b/theme/components/Menu.ts index fb314ddcfd..5e425ff738 100644 --- a/theme/components/Menu.ts +++ b/theme/components/Menu.ts @@ -25,15 +25,15 @@ const baseStyleList = defineStyle({ const baseStyleItem = defineStyle({ _focus: { - [$bg.variable]: 'colors.blue.50', + [$bg.variable]: 'transparent', _dark: { - [$bg.variable]: 'colors.gray.800', + [$bg.variable]: 'transparent', }, }, _hover: { [$bg.variable]: 'colors.blue.50', _dark: { - [$bg.variable]: 'colors.gray.800', + [$bg.variable]: 'colors.whiteAlpha.100', }, }, bg: $bg.reference, diff --git a/theme/foundations/colors.ts b/theme/foundations/colors.ts index 8ad22f3fbd..f8b2ca521f 100644 --- a/theme/foundations/colors.ts +++ b/theme/foundations/colors.ts @@ -55,7 +55,7 @@ const colors = { linkedin: '#1564BA', discord: '#9747FF', slack: '#1BA27A', - twitter: '#63B3ED', + twitter: '#000000', opensea: '#2081E2', facebook: '#4460A0', medium: '#231F20', diff --git a/tools/scripts/pw.docker.deps.sh b/tools/scripts/pw.docker.deps.sh new file mode 100755 index 0000000000..719930361f --- /dev/null +++ b/tools/scripts/pw.docker.deps.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +yarn install --modules-folder node_modules_linux diff --git a/tools/scripts/pw.docker.sh b/tools/scripts/pw.docker.sh index 30179e8c4c..49c763f9ad 100755 --- a/tools/scripts/pw.docker.sh +++ b/tools/scripts/pw.docker.sh @@ -1,7 +1,5 @@ #!/bin/bash -yarn install --modules-folder node_modules_linux - export NODE_PATH=$(pwd)/node_modules_linux yarn test:pw "$@" diff --git a/types/api/account.ts b/types/api/account.ts index 101b746ed0..4279884406 100644 --- a/types/api/account.ts +++ b/types/api/account.ts @@ -36,6 +36,7 @@ export interface NotificationSettings { 'native': NotificationDirection; 'ERC-20': NotificationDirection; 'ERC-721': NotificationDirection; + 'ERC-404': NotificationDirection; } export interface NotificationMethods { @@ -69,7 +70,7 @@ export type Transactions = Array export interface UserInfo { name?: string; nickname?: string; - email: string; + email: string | null; avatar?: string; } diff --git a/types/api/block.ts b/types/api/block.ts index 3480660065..2fc14d2745 100644 --- a/types/api/block.ts +++ b/types/api/block.ts @@ -2,6 +2,8 @@ import type { AddressParam } from 'types/api/addressParams'; import type { Reward } from 'types/api/reward'; import type { Transaction } from 'types/api/transaction'; +import type { ZkSyncBatchesItem } from './zkSyncL2'; + export type BlockType = 'block' | 'reorg' | 'uncle'; export interface Block { @@ -42,6 +44,10 @@ export interface Block { burnt_blob_fees?: string; excess_blob_gas?: string; blob_tx_count?: number; + // ZKSYNC FIELDS + zksync?: Omit & { + 'batch_number': number | null; + }; } export interface BlocksResponse { diff --git a/types/api/contract.ts b/types/api/contract.ts index b48229f9eb..fb85eb9bdf 100644 --- a/types/api/contract.ts +++ b/types/api/contract.ts @@ -178,6 +178,7 @@ export interface SmartContractVerificationError { export type SolidityscanReport = { scan_report: { + contractname: string; scan_status: string; scan_summary: { issue_severity_distribution: { diff --git a/types/api/noves.ts b/types/api/noves.ts new file mode 100644 index 0000000000..2f85ca6391 --- /dev/null +++ b/types/api/noves.ts @@ -0,0 +1,125 @@ +export interface NovesResponseData { + txTypeVersion: number; + chain: string; + accountAddress: string; + classificationData: NovesClassificationData; + rawTransactionData: NovesRawTransactionData; +} + +export interface NovesClassificationData { + type: string; + typeFormatted?: string; + description: string; + sent: Array; + received: Array; + approved?: Approved; + protocol?: { + name: string | null; + }; + source: { + type: string | null; + }; + message?: string; + deployedContractAddress?: string; +} + +export interface Approved { + amount: string; + spender: string; + token?: NovesToken; + nft?: NovesNft; +} + +export interface NovesSentReceived { + action: string; + actionFormatted?: string; + amount: string; + to: NovesTo; + from: NovesFrom; + token?: NovesToken; + nft?: NovesNft; +} + +export interface NovesToken { + symbol: string; + name: string; + decimals: number; + address: string; + id?: string; +} + +export interface NovesNft { + name: string; + id: string; + symbol: string; + address: string; +} + +export interface NovesFrom { + name: string | null; + address: string; +} + +export interface NovesTo { + name: string | null; + address: string | null; +} + +export interface NovesRawTransactionData { + transactionHash: string; + fromAddress: string; + toAddress: string; + blockNumber: number; + gas: number; + gasPrice: number; + transactionFee: NovesTransactionFee | number; + timestamp: number; +} + +export interface NovesTransactionFee { + amount: string; + currency?: string; + token?: { + decimals: number; + symbol: string; + }; +} + +export interface NovesAccountHistoryResponse { + hasNextPage: boolean; + items: Array; + pageNumber: number; + pageSize: number; + next_page_params?: { + startBlock: string; + endBlock: string; + pageNumber: number; + pageSize: number; + ignoreTransactions: string; + viewAsAccountAddress: string; + }; +} + +export const NovesHistoryFilterValues = [ 'received', 'sent' ] as const; + +export type NovesHistoryFilterValue = typeof NovesHistoryFilterValues[number] | undefined; + +export interface NovesHistoryFilters { + filter?: NovesHistoryFilterValue; +} + +export interface NovesDescribeResponse { + type: string; + description: string; +} + +export interface NovesDescribeTxsResponse { + txHash: string; + type: string; + description: string; +}[]; + +export interface NovesTxTranslation { + data?: NovesDescribeTxsResponse; + isLoading: boolean; +} diff --git a/types/api/token.ts b/types/api/token.ts index 6edb4c60ec..7cae408f7d 100644 --- a/types/api/token.ts +++ b/types/api/token.ts @@ -1,7 +1,7 @@ import type { TokenInfoApplication } from './account'; import type { AddressParam } from './addressParams'; -export type NFTTokenType = 'ERC-721' | 'ERC-1155'; +export type NFTTokenType = 'ERC-721' | 'ERC-1155' | 'ERC-404'; export type TokenType = 'ERC-20' | NFTTokenType; export interface TokenInfo { diff --git a/types/api/tokenTransfer.ts b/types/api/tokenTransfer.ts index 860d14455a..bb580516dd 100644 --- a/types/api/tokenTransfer.ts +++ b/types/api/tokenTransfer.ts @@ -16,6 +16,14 @@ export type Erc1155TotalPayload = { token_id: string | null; } +export type Erc404TotalPayload = { + decimals: string; + value: string; + token_id: null; +} | { + token_id: string; +}; + export type TokenTransfer = ( { token: TokenInfo<'ERC-20'>; @@ -28,6 +36,10 @@ export type TokenTransfer = ( { token: TokenInfo<'ERC-1155'>; total: Erc1155TotalPayload; + } | + { + token: TokenInfo<'ERC-404'>; + total: Erc404TotalPayload; } ) & TokenTransferBase diff --git a/types/api/transaction.ts b/types/api/transaction.ts index 53ce2ead22..8f1fee017e 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -2,10 +2,12 @@ import type { AddressParam } from './addressParams'; import type { BlockTransactionsResponse } from './block'; import type { DecodedInput } from './decodedInput'; import type { Fee } from './fee'; +import type { NovesTxTranslation } from './noves'; import type { OptimisticL2WithdrawalStatus } from './optimisticL2'; import type { TokenInfo } from './token'; import type { TokenTransfer } from './tokenTransfer'; import type { TxAction } from './txAction'; +import type { ZkSyncBatchesItem } from './zkSyncL2'; export type TransactionRevertReason = { raw: string; @@ -79,12 +81,18 @@ export type Transaction = { zkevm_batch_number?: number; zkevm_status?: typeof ZKEVM_L2_TX_STATUSES[number]; zkevm_sequence_hash?: string; + // zkSync FIELDS + zksync?: Omit & { + 'batch_number': number | null; + }; // blob tx fields blob_versioned_hashes?: Array; blob_gas_used?: string; blob_gas_price?: string; burnt_blob_fee?: string; max_fee_per_blob_gas?: string; + // Noves-fi + translation?: NovesTxTranslation; } export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ]; diff --git a/types/api/txStateChanges.ts b/types/api/txStateChanges.ts index 459f196aff..284a921699 100644 --- a/types/api/txStateChanges.ts +++ b/types/api/txStateChanges.ts @@ -41,6 +41,13 @@ export interface TxStateChangeTokenErc1155 { token_id: string; } +export interface TxStateChangeTokenErc404 { + type: 'token'; + token: TokenInfo<'ERC-404'>; + change: string; + token_id: string; +} + export type TxStateChanges = { items: Array; next_page_params: { diff --git a/types/api/zkSyncL2.ts b/types/api/zkSyncL2.ts new file mode 100644 index 0000000000..4d038477ff --- /dev/null +++ b/types/api/zkSyncL2.ts @@ -0,0 +1,52 @@ +import type { Transaction } from './transaction'; + +export const ZKSYNC_L2_TX_BATCH_STATUSES = [ + 'Processed on L2' as const, + 'Sealed on L2' as const, + 'Sent to L1' as const, + 'Validated on L1' as const, + 'Executed on L1' as const, +]; + +export type ZkSyncBatchStatus = typeof ZKSYNC_L2_TX_BATCH_STATUSES[number]; + +export interface ZkSyncBatchesItem { + commit_transaction_hash: string | null; + commit_transaction_timestamp: string | null; + execute_transaction_hash: string | null; + execute_transaction_timestamp: string | null; + number: number; + prove_transaction_hash: string | null; + prove_transaction_timestamp: string | null; + status: ZkSyncBatchStatus; + timestamp: string; + tx_count: number; +} + +export type ZkSyncBatchesResponse = { + items: Array; + next_page_params: { + number: number; + items_count: number; + } | null; +} + +export interface ZkSyncBatch extends Omit { + start_block: number; + end_block: number; + l1_gas_price: string; + l1_tx_count: number; + l2_fair_gas_price: string; + l2_tx_count: number; + root_hash: string; +} + +export type ZkSyncBatchTxs = { + items: Array; + next_page_params: { + batch_number: string; + block_number: number; + index: number; + items_count: number; + } | null; +} diff --git a/types/client/adProviders.ts b/types/client/adProviders.ts index 0f81cee1f0..ac0418496d 100644 --- a/types/client/adProviders.ts +++ b/types/client/adProviders.ts @@ -3,5 +3,8 @@ import type { ArrayElement } from 'types/utils'; export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'hype', 'getit', 'none' ] as const; export type AdBannerProviders = ArrayElement; +export const SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS = [ 'adbutler' ] as const; +export type AdBannerAdditionalProviders = ArrayElement; + export const SUPPORTED_AD_TEXT_PROVIDERS = [ 'coinzilla', 'none' ] as const; export type AdTextProviders = ArrayElement; diff --git a/types/client/marketplace.ts b/types/client/marketplace.ts index 58ede5b258..9063f538af 100644 --- a/types/client/marketplace.ts +++ b/types/client/marketplace.ts @@ -1,3 +1,5 @@ +import type { SolidityscanReport } from 'types/api/contract'; + export type MarketplaceAppPreview = { id: string; external?: boolean; @@ -24,7 +26,45 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia site?: string; } +export type MarketplaceAppWithSecurityReport = MarketplaceAppOverview & { + securityReport?: MarketplaceAppSecurityReport; +} + export enum MarketplaceCategory { ALL = 'All', FAVORITES = 'Favorites', } + +export enum ContractListTypes { + ANALYZED = 'Analyzed', + ALL = 'All', + VERIFIED = 'Verified', +} + +export enum MarketplaceDisplayType { + DEFAULT = 'default', + SCORES = 'scores', +} + +export type MarketplaceAppSecurityReport = { + overallInfo: { + verifiedNumber: number; + totalContractsNumber: number; + solidityScanContractsNumber: number; + securityScore: number; + totalIssues?: number; + issueSeverityDistribution: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + }; + contractsData: Array<{ + address: string; + isVerified: boolean; + solidityScanReport?: SolidityscanReport['scan_report'] | null; + }>; +} + +export type MarketplaceAppSecurityReportRaw = { + appName: string; + chainsData: { + [chainId: string]: MarketplaceAppSecurityReport; + }; +} diff --git a/types/client/rollup.ts b/types/client/rollup.ts index 2c62078efc..3e5e5da91d 100644 --- a/types/client/rollup.ts +++ b/types/client/rollup.ts @@ -4,6 +4,7 @@ export const ROLLUP_TYPES = [ 'optimistic', 'shibarium', 'zkEvm', + 'zkSync', ] as const; export type RollupType = ArrayElement; diff --git a/types/client/txInterpretation.ts b/types/client/txInterpretation.ts index e264b267bc..23f55ed217 100644 --- a/types/client/txInterpretation.ts +++ b/types/client/txInterpretation.ts @@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils'; export const PROVIDERS = [ 'blockscout', + 'noves', 'none', ] as const; diff --git a/types/utils.ts b/types/utils.ts index 6a41f16f0c..06bb70df3e 100644 --- a/types/utils.ts +++ b/types/utils.ts @@ -7,3 +7,7 @@ export type ExcludeNull = T extends null ? never : T; export type ExcludeUndefined = T extends undefined ? never : T; export type KeysOfObjectOrNull = keyof ExcludeNull; + +/** Combines members of an intersection into a readable type. */ +// https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg +export type Evaluate = { [key in keyof Type]: Type[key] } & unknown diff --git a/types/views/tx.ts b/types/views/tx.ts index dc6a750d27..21800d80e0 100644 --- a/types/views/tx.ts +++ b/types/views/tx.ts @@ -16,9 +16,3 @@ export const TX_ADDITIONAL_FIELDS_IDS = [ ] as const; export type TxAdditionalFieldsId = ArrayElement; - -export const TX_VIEWS_IDS = [ - 'blob_txs', -] as const; - -export type TxViewId = ArrayElement; diff --git a/types/web3.ts b/types/web3.ts new file mode 100644 index 0000000000..1eab7a8cfa --- /dev/null +++ b/types/web3.ts @@ -0,0 +1,65 @@ +// copied from node_modules/@wagmi/core/src/connectors/injected.ts +import type { EIP1193Provider } from 'viem'; + +import type { Evaluate } from './utils'; + +type WalletProviderFlags = + | 'isApexWallet' + | 'isAvalanche' + | 'isBackpack' + | 'isBifrost' + | 'isBitKeep' + | 'isBitski' + | 'isBlockWallet' + | 'isBraveWallet' + | 'isCoinbaseWallet' + | 'isDawn' + | 'isEnkrypt' + | 'isExodus' + | 'isFrame' + | 'isFrontier' + | 'isGamestop' + | 'isHyperPay' + | 'isImToken' + | 'isKuCoinWallet' + | 'isMathWallet' + | 'isMetaMask' + | 'isOkxWallet' + | 'isOKExWallet' + | 'isOneInchAndroidWallet' + | 'isOneInchIOSWallet' + | 'isOpera' + | 'isPhantom' + | 'isPortal' + | 'isRabby' + | 'isRainbow' + | 'isStatus' + | 'isTally' + | 'isTokenPocket' + | 'isTokenary' + | 'isTrust' + | 'isTrustWallet' + | 'isXDEFI' + | 'isZerion' + +export type WalletProvider = Evaluate< +EIP1193Provider & { + [key in WalletProviderFlags]?: true | undefined +} & { + providers?: Array | undefined; + + /** Only exists in MetaMask as of 2022/04/03 */ + _events?: { connect?: (() => void) | undefined } | undefined; + + /** Only exists in MetaMask as of 2022/04/03 */ + _state?: + | { + accounts?: Array; + initialized?: boolean; + isConnected?: boolean; + isPermanentlyDisconnected?: boolean; + isUnlocked?: boolean; + } + | undefined; +} +> diff --git a/ui/address/AddressAccountHistory.tsx b/ui/address/AddressAccountHistory.tsx new file mode 100644 index 0000000000..5b7cf3aa7e --- /dev/null +++ b/ui/address/AddressAccountHistory.tsx @@ -0,0 +1,125 @@ +import { Box, Hide, Show, Table, + Tbody, Th, Tr } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { NovesHistoryFilterValue } from 'types/api/noves'; +import { NovesHistoryFilterValues } from 'types/api/noves'; + +import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate'; +import { generateListStub } from 'stubs/utils'; +import AddressAccountHistoryTableItem from 'ui/address/accountHistory/AddressAccountHistoryTableItem'; +import ActionBar from 'ui/shared/ActionBar'; +import DataListDisplay from 'ui/shared/DataListDisplay'; +import { getFromToValue } from 'ui/shared/Noves/utils'; +import Pagination from 'ui/shared/pagination/Pagination'; +import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import TheadSticky from 'ui/shared/TheadSticky'; + +import AddressAccountHistoryListItem from './accountHistory/AddressAccountHistoryListItem'; +import AccountHistoryFilter from './AddressAccountHistoryFilter'; + +const getFilterValue = (getFilterValueFromQuery).bind(null, NovesHistoryFilterValues); + +type Props = { + scrollRef?: React.RefObject; +} + +const AddressAccountHistory = ({ scrollRef }: Props) => { + const router = useRouter(); + + const currentAddress = getQueryParamString(router.query.hash).toLowerCase(); + + const [ filterValue, setFilterValue ] = React.useState(getFilterValue(router.query.filter)); + + const { data, isError, pagination, isPlaceholderData } = useQueryWithPages({ + resourceName: 'noves_address_history', + pathParams: { address: currentAddress }, + scrollRef, + options: { + placeholderData: generateListStub<'noves_address_history'>(NOVES_TRANSLATE, 10, { hasNextPage: false, pageNumber: 1, pageSize: 10 }), + }, + }); + + const handleFilterChange = React.useCallback((val: string | Array) => { + + const newVal = getFilterValue(val); + setFilterValue(newVal); + }, [ ]); + + const actionBar = ( + + + + + + ); + + const filteredData = isPlaceholderData ? data?.items : data?.items.filter(i => filterValue ? getFromToValue(i, currentAddress) === filterValue : i); + + const content = ( + + + { filteredData?.map((item, i) => ( + + )) } + + + + + + + + + + + + + { filteredData?.map((item, i) => ( + + )) } + +
+ Age + + Action + + From/To +
+
+
+ ); + + return ( + + ); +}; + +export default AddressAccountHistory; diff --git a/ui/address/AddressAccountHistoryFilter.tsx b/ui/address/AddressAccountHistoryFilter.tsx new file mode 100644 index 0000000000..d66519d635 --- /dev/null +++ b/ui/address/AddressAccountHistoryFilter.tsx @@ -0,0 +1,55 @@ +import { + Menu, + MenuButton, + MenuList, + MenuOptionGroup, + MenuItemOption, + useDisclosure, +} from '@chakra-ui/react'; +import React from 'react'; + +import type { NovesHistoryFilterValue } from 'types/api/noves'; + +import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; +import FilterButton from 'ui/shared/filters/FilterButton'; + +interface Props { + isActive: boolean; + defaultFilter: NovesHistoryFilterValue; + onFilterChange: (nextValue: string | Array) => void; + isLoading?: boolean; +} + +const AccountHistoryFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }: Props) => { + const { isOpen, onToggle } = useDisclosure(); + const isInitialLoading = useIsInitialLoading(isLoading); + + const onCloseMenu = React.useCallback(() => { + if (isOpen) { + onToggle(); + } + }, [ isOpen, onToggle ]); + + return ( + + + + + + + All + Received from + Sent to + + + + ); +}; + +export default React.memo(AccountHistoryFilter); diff --git a/ui/address/AddressDetails.pw.tsx b/ui/address/AddressDetails.pw.tsx index 26920f92e1..310c175141 100644 --- a/ui/address/AddressDetails.pw.tsx +++ b/ui/address/AddressDetails.pw.tsx @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import type { WindowProvider } from 'wagmi'; + +import type { WalletProvider } from 'types/web3'; import * as addressMock from 'mocks/address/address'; import * as countersMock from 'mocks/address/counters'; @@ -19,6 +20,7 @@ const API_URL_COUNTERS = buildApiUrl('address_counters', { hash: ADDRESS_HASH }) const API_URL_TOKENS_ERC20 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-20'; const API_URL_TOKENS_ERC721 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-721'; const API_URL_TOKENS_ER1155 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-1155'; +const API_URL_TOKENS_ERC404 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-404'; const hooksConfig = { router: { query: { hash: ADDRESS_HASH }, @@ -69,11 +71,15 @@ test('token', async({ mount, page }) => { status: 200, body: JSON.stringify(tokensMock.erc1155List), }), { times: 1 }); + await page.route(API_URL_TOKENS_ERC404, async(route) => route.fulfill({ + status: 200, + body: JSON.stringify(tokensMock.erc404List), + }), { times: 1 }); await page.evaluate(() => { window.ethereum = { providers: [ { isMetaMask: true, _events: {} } ], - }as WindowProvider; + } as WalletProvider; }); const component = await mount( diff --git a/ui/address/AddressTokens.pw.tsx b/ui/address/AddressTokens.pw.tsx index 7ee5556986..514b3aa7ad 100644 --- a/ui/address/AddressTokens.pw.tsx +++ b/ui/address/AddressTokens.pw.tsx @@ -37,6 +37,10 @@ const test = base.extend({ items: [ tokensMock.erc1155a, tokensMock.erc1155b ], next_page_params: nextPageParams, }; + const response404 = { + items: [ tokensMock.erc404a, tokensMock.erc404b ], + next_page_params: nextPageParams, + }; await page.route(API_URL_ADDRESS, (route) => route.fulfill({ status: 200, @@ -54,6 +58,10 @@ const test = base.extend({ status: 200, body: JSON.stringify(response1155), })); + await page.route(API_URL_TOKENS + '?type=ERC-404', (route) => route.fulfill({ + status: 200, + body: JSON.stringify(response404), + })); await page.route(API_URL_NFT, (route) => route.fulfill({ status: 200, body: JSON.stringify(tokensMock.nfts), @@ -217,6 +225,10 @@ base.describe('update balances via socket', () => { items: [ tokensMock.erc1155a ], next_page_params: null, }; + const response404 = { + items: [ tokensMock.erc404a ], + next_page_params: null, + }; await page.route(API_URL_ADDRESS, (route) => route.fulfill({ status: 200, @@ -234,6 +246,10 @@ base.describe('update balances via socket', () => { status: 200, body: JSON.stringify(response1155), })); + await page.route(API_URL_TOKENS + '?type=ERC-404', (route) => route.fulfill({ + status: 200, + body: JSON.stringify(response404), + })); const component = await mount( @@ -248,6 +264,7 @@ base.describe('update balances via socket', () => { await page.waitForResponse(API_URL_TOKENS + '?type=ERC-20'); await page.waitForResponse(API_URL_TOKENS + '?type=ERC-721'); await page.waitForResponse(API_URL_TOKENS + '?type=ERC-1155'); + await page.waitForResponse(API_URL_TOKENS + '?type=ERC-404'); await expect(component).toHaveScreenshot(); diff --git a/ui/address/AddressTokens.tsx b/ui/address/AddressTokens.tsx index 0e27a73f7d..ea0059232e 100644 --- a/ui/address/AddressTokens.tsx +++ b/ui/address/AddressTokens.tsx @@ -28,7 +28,8 @@ import TokenBalances from './tokens/TokenBalances'; type TNftDisplayType = 'collection' | 'list'; const TAB_LIST_PROPS = { - my: 3, + mt: 1, + mb: { base: 6, lg: 1 }, py: 5, columnGap: 3, }; diff --git a/ui/address/AddressTxs.tsx b/ui/address/AddressTxs.tsx index cc21c355df..d272982c71 100644 --- a/ui/address/AddressTxs.tsx +++ b/ui/address/AddressTxs.tsx @@ -20,6 +20,7 @@ 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 { sortTxsFromSocket } from 'ui/txs/sortTxs'; import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting'; import { SORT_OPTIONS } from 'ui/txs/useTxsSort'; @@ -85,7 +86,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { addressTxsQuery.onFilterChange({ filter: newVal }); }, [ addressTxsQuery ]); - const handleNewSocketMessage: SocketMessage.AddressTxs['handler'] = (payload) => { + const handleNewSocketMessage: SocketMessage.AddressTxs['handler'] = React.useCallback((payload) => { setSocketAlert(''); queryClient.setQueryData( @@ -123,10 +124,10 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { items: [ ...newItems, ...prevData.items, - ], + ].sort(sortTxsFromSocket(sort)), }; }); - }; + }, [ currentAddress, filterValue, overloadCount, queryClient, sort ]); const handleSocketClose = React.useCallback(() => { setSocketAlert('Connection is lost. Please refresh the page to load new transactions.'); diff --git a/ui/address/AddressTxsFilter.tsx b/ui/address/AddressTxsFilter.tsx index c566f1d91e..c08601123a 100644 --- a/ui/address/AddressTxsFilter.tsx +++ b/ui/address/AddressTxsFilter.tsx @@ -36,7 +36,7 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading } /> - + All Outgoing transactions Incoming transactions diff --git a/ui/address/SolidityscanReport.tsx b/ui/address/SolidityscanReport.tsx index d7bbdc9ca2..c3a7ae2669 100644 --- a/ui/address/SolidityscanReport.tsx +++ b/ui/address/SolidityscanReport.tsx @@ -1,72 +1,22 @@ -import { - Box, - Flex, - Text, - Grid, - Button, - chakra, - Popover, - PopoverTrigger, - PopoverBody, - PopoverContent, - useDisclosure, - Skeleton, - Center, - useColorModeValue, -} from '@chakra-ui/react'; +import { Box, Text, chakra, Icon, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; import React from 'react'; -import { SolidityscanReport } from 'types/api/contract'; - +// This icon doesn't work properly when it is in the sprite +// Probably because of the gradient +// eslint-disable-next-line no-restricted-imports +import solidityScanIcon from 'icons/brands/solidity_scan.svg'; 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' }, -]; +import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; +import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; +import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; 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(); @@ -80,31 +30,10 @@ const SolidityscanReport = ({ className, hash }: Props) => { 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); @@ -112,57 +41,25 @@ const SolidityscanReport = ({ className, hash }: Props) => { return ( - - - + - Contract analyzed for 140+ vulnerability patterns by SolidityScan - - -
- -
-
- - - { score } - / 100 - - Security score is { scoreLevel } - -
+ + Contract analyzed for 160+ vulnerability patterns by + + SolidityScan + + { vulnerabilities && vulnerabilitiesCount > 0 && ( Vulnerabilities distribution - - { DISTRIBUTION_ITEMS.map(item => ( - - )) } - + ) } View full report diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png index 28d70e3dfe..a41b419d9b 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png index dcdcb70660..28f43e103b 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png index 0356e2f168..beb7cd8b55 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png differ 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 index 2139d224d0..86b0488857 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png 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_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png index 4a65f689f5..a9bf89c441 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_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png index 866b983bed..d38ef06070 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png 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 index 9cac0ae583..baa0f2ec89 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png 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_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png index 7cb23eb41f..f570e561c8 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_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png index cddaa792d1..d1d4f7eab1 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png 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-erc20-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png index 00c4705d45..ac6aa9b71b 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-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png index 297f61b8c3..eca7f88c7f 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png 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 index a489eed6cf..6a7c63dc6e 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png 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 6c3062b241..928a20abf3 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 61d8c17ad8..809e53fa3e 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-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png index 30011e3179..c25741b0da 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png 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-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png index 64a8c4c7d4..fc6141ef12 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png 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-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png index 7f849f73f4..c6b9450434 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png 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-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png index a357f25316..af84b4459b 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png 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-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png index 8f4b2b9099..eca7e9027e 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/accountHistory/AddressAccountHistoryListItem.tsx b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx new file mode 100644 index 0000000000..46cf969932 --- /dev/null +++ b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx @@ -0,0 +1,66 @@ +import { Box, Flex, Skeleton, Text } from '@chakra-ui/react'; +import React, { useMemo } from 'react'; + +import type { NovesResponseData } from 'types/api/noves'; + +import dayjs from 'lib/date/dayjs'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkInternal from 'ui/shared/LinkInternal'; +import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; +import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; + +type Props = { + isPlaceholderData: boolean; + tx: NovesResponseData; + currentAddress: string; +}; + +const AddressAccountHistoryListItem = (props: Props) => { + + const parsedDescription = useMemo(() => { + const description = props.tx.classificationData.description; + + return description.endsWith('.') ? description.substring(0, description.length - 1) : description; + }, [ props.tx.classificationData.description ]); + + return ( + + + + + + + + Action + + + + { dayjs(props.tx.rawTransactionData.timestamp * 1000).fromNow() } + + + + + + { parsedDescription } + + + + + + + + ); +}; + +export default React.memo(AddressAccountHistoryListItem); diff --git a/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx new file mode 100644 index 0000000000..c3aa61a283 --- /dev/null +++ b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx @@ -0,0 +1,66 @@ +import { Td, Tr, Skeleton, Text, Box } from '@chakra-ui/react'; +import React, { useMemo } from 'react'; + +import type { NovesResponseData } from 'types/api/noves'; + +import dayjs from 'lib/date/dayjs'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkInternal from 'ui/shared/LinkInternal'; +import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; + +type Props = { + isPlaceholderData: boolean; + tx: NovesResponseData; + currentAddress: string; +}; + +const AddressAccountHistoryTableItem = (props: Props) => { + + const parsedDescription = useMemo(() => { + const description = props.tx.classificationData.description; + + return description.endsWith('.') ? description.substring(0, description.length - 1) : description; + }, [ props.tx.classificationData.description ]); + + return ( + + + + + { dayjs(props.tx.rawTransactionData.timestamp * 1000).fromNow() } + + + + + + + + + + { parsedDescription } + + + + + + + + + + + ); +}; + +export default React.memo(AddressAccountHistoryTableItem); diff --git a/ui/address/contract/ContractWrite.tsx b/ui/address/contract/ContractWrite.tsx index 3e68421ed2..1b282fb508 100644 --- a/ui/address/contract/ContractWrite.tsx +++ b/ui/address/contract/ContractWrite.tsx @@ -1,6 +1,6 @@ import { useRouter } from 'next/router'; import React from 'react'; -import { useAccount, useWalletClient, useNetwork, useSwitchNetwork } from 'wagmi'; +import { useAccount, useWalletClient, useSwitchChain } from 'wagmi'; import type { SmartContractWriteMethod } from 'types/api/contract'; @@ -21,9 +21,8 @@ import { getNativeCoinValue, prepareAbi } from './utils'; const ContractWrite = () => { const { data: walletClient } = useWalletClient(); - const { isConnected } = useAccount(); - const { chain } = useNetwork(); - const { switchNetworkAsync } = useSwitchNetwork(); + const { isConnected, chainId } = useAccount(); + const { switchChainAsync } = useSwitchChain(); const router = useRouter(); @@ -45,14 +44,13 @@ const ContractWrite = () => { const contractAbi = useContractAbi({ addressHash, isProxy, isCustomAbi }); - // TODO @tom2drum maybe move this inside the form const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array) => { if (!isConnected) { throw new Error('Wallet is not connected'); } - if (chain?.id && String(chain.id) !== config.chain.id) { - await switchNetworkAsync?.(Number(config.chain.id)); + if (chainId && String(chainId) !== config.chain.id) { + await switchChainAsync?.({ chainId: Number(config.chain.id) }); } if (!contractAbi) { @@ -87,7 +85,7 @@ const ContractWrite = () => { }); return { hash }; - }, [ isConnected, chain, contractAbi, walletClient, addressHash, switchNetworkAsync ]); + }, [ isConnected, chainId, contractAbi, walletClient, addressHash, switchChainAsync ]); const renderItemContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => { return ( diff --git a/ui/address/contract/ContractWriteResult.tsx b/ui/address/contract/ContractWriteResult.tsx index 266f64dd03..3cc755d131 100644 --- a/ui/address/contract/ContractWriteResult.tsx +++ b/ui/address/contract/ContractWriteResult.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useWaitForTransaction } from 'wagmi'; +import { useWaitForTransactionReceipt } from 'wagmi'; import type { ResultComponentProps } from './methodForm/types'; import type { ContractMethodWriteResult } from './types'; @@ -9,7 +9,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb'; const ContractWriteResult = ({ result, onSettle }: ResultComponentProps) => { const txHash = result && 'hash' in result ? result.hash as `0x${ string }` : undefined; - const txInfo = useWaitForTransaction({ + const txInfo = useWaitForTransactionReceipt({ hash: txHash, }); diff --git a/ui/address/contract/ContractWriteResultDumb.pw.tsx b/ui/address/contract/ContractWriteResultDumb.pw.tsx index e082542ff0..e916ab90c6 100644 --- a/ui/address/contract/ContractWriteResultDumb.pw.tsx +++ b/ui/address/contract/ContractWriteResultDumb.pw.tsx @@ -8,7 +8,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb'; test('loading', async({ mount }) => { const props = { txInfo: { - status: 'loading' as const, + status: 'pending' as const, error: null, }, result: { diff --git a/ui/address/contract/ContractWriteResultDumb.tsx b/ui/address/contract/ContractWriteResultDumb.tsx index 6e734f93a1..3b898fb570 100644 --- a/ui/address/contract/ContractWriteResultDumb.tsx +++ b/ui/address/contract/ContractWriteResultDumb.tsx @@ -11,7 +11,7 @@ interface Props { result: ContractMethodWriteResult; onSettle: () => void; txInfo: { - status: 'loading' | 'success' | 'error' | 'idle'; + status: 'loading' | 'success' | 'error' | 'idle' | 'pending'; error: Error | null; }; } @@ -20,7 +20,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { const txHash = result && 'hash' in result ? result.hash : undefined; React.useEffect(() => { - if (txInfo.status !== 'loading') { + if (txInfo.status !== 'pending') { onSettle(); } }, [ onSettle, txInfo.status ]); @@ -55,7 +55,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ); } - case 'loading': { + case 'pending': { return ( <> diff --git a/ui/address/contract/methodForm/ContractMethodForm.tsx b/ui/address/contract/methodForm/ContractMethodForm.tsx index 5bcd5e3af6..d1e969b334 100644 --- a/ui/address/contract/methodForm/ContractMethodForm.tsx +++ b/ui/address/contract/methodForm/ContractMethodForm.tsx @@ -1,4 +1,5 @@ import { Box, Button, Flex, chakra } from '@chakra-ui/react'; +import _mapValues from 'lodash/mapValues'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; @@ -35,7 +36,11 @@ const ContractMethodForm = ({ data, onSubmit, res }); const onFormSubmit: SubmitHandler = React.useCallback(async(formData) => { - const args = transformFormDataToMethodArgs(formData); + // The API used for reading from contracts expects all values to be strings. + const formattedData = methodType === 'read' ? + _mapValues(formData, (value) => value !== undefined ? String(value) : undefined) : + formData; + const args = transformFormDataToMethodArgs(formattedData); setResult(undefined); setLoading(true); diff --git a/ui/address/contract/methodForm/useArgTypeMatchInt.tsx b/ui/address/contract/methodForm/useArgTypeMatchInt.tsx index bb3f375e7c..fa6b994021 100644 --- a/ui/address/contract/methodForm/useArgTypeMatchInt.tsx +++ b/ui/address/contract/methodForm/useArgTypeMatchInt.tsx @@ -9,8 +9,8 @@ interface Params { export interface MatchInt { isUnsigned: boolean; power: string; - min: number; - max: number; + min: bigint; + max: bigint; } export default function useArgTypeMatchInt({ argType }: Params): MatchInt | null { diff --git a/ui/address/contract/methodForm/useFormatFieldValue.tsx b/ui/address/contract/methodForm/useFormatFieldValue.tsx index c658694b5f..6c82c682b3 100644 --- a/ui/address/contract/methodForm/useFormatFieldValue.tsx +++ b/ui/address/contract/methodForm/useFormatFieldValue.tsx @@ -17,6 +17,9 @@ export default function useFormatFieldValue({ argType, argTypeMatchInt }: Params } if (argTypeMatchInt) { + // we have to store all numbers as strings to avoid precision loss + // and we cannot store them as BigInt because the NumberFormat component will not work properly + // so we just remove all white spaces here otherwise the `viem` library will throw an error on attempt to write value to a contract const formattedString = value.replace(/\s/g, ''); return formattedString; } @@ -26,11 +29,11 @@ export default function useFormatFieldValue({ argType, argTypeMatchInt }: Params switch (formattedValue) { case 'true': { - return 'true'; + return true; } case 'false':{ - return 'false'; + return false; } default: diff --git a/ui/address/contract/methodForm/useValidateField.tsx b/ui/address/contract/methodForm/useValidateField.tsx index d14174d789..5151cffcba 100644 --- a/ui/address/contract/methodForm/useValidateField.tsx +++ b/ui/address/contract/methodForm/useValidateField.tsx @@ -18,13 +18,15 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt return argType.match(BYTES_REGEXP); }, [ argType ]); - return React.useCallback((value: string | undefined) => { + // some values are formatted before they are sent to the validator + // see ./useFormatFieldValue.tsx hook + return React.useCallback((value: string | boolean | undefined) => { if (value === undefined || value === '') { return isOptional ? true : 'Field is required'; } if (argType === 'address') { - if (!isAddress(value)) { + if (typeof value !== 'string' || !isAddress(value)) { return 'Invalid address format'; } @@ -39,13 +41,19 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt } if (argTypeMatchInt) { - const formattedValue = Number(value); - - if (Object.is(formattedValue, NaN)) { + const valueBi = (() => { + try { + return BigInt(value); + } catch (error) { + return null; + } + })(); + + if (typeof value !== 'string' || valueBi === null) { return 'Invalid integer format'; } - if (formattedValue > argTypeMatchInt.max || formattedValue < argTypeMatchInt.min) { + if (valueBi > argTypeMatchInt.max || valueBi < argTypeMatchInt.min) { const lowerBoundary = argTypeMatchInt.isUnsigned ? '0' : `-1 * 2 ^ ${ Number(argTypeMatchInt.power) - 1 }`; const upperBoundary = argTypeMatchInt.isUnsigned ? `2 ^ ${ argTypeMatchInt.power } - 1` : `2 ^ ${ Number(argTypeMatchInt.power) - 1 } - 1`; return `Value should be in range from "${ lowerBoundary }" to "${ upperBoundary }" inclusively`; @@ -55,7 +63,7 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt } if (argType === 'bool') { - if (value !== 'true' && value !== 'false') { + if (typeof value !== 'boolean') { return 'Invalid boolean format. Allowed values: true, false'; } } diff --git a/ui/address/contract/methodForm/utils.ts b/ui/address/contract/methodForm/utils.ts index 117a963e85..61515577d3 100644 --- a/ui/address/contract/methodForm/utils.ts +++ b/ui/address/contract/methodForm/utils.ts @@ -2,7 +2,7 @@ import _set from 'lodash/set'; import type { SmartContractMethodInput } from 'types/api/contract'; -export type ContractMethodFormFields = Record; +export type ContractMethodFormFields = Record; export const INT_REGEXP = /^(u)?int(\d+)?$/i; @@ -11,9 +11,9 @@ export const BYTES_REGEXP = /^bytes(\d+)?$/i; export const ARRAY_REGEXP = /^(.*)\[(\d*)\]$/; export const getIntBoundaries = (power: number, isUnsigned: boolean) => { - const maxUnsigned = 2 ** power; - const max = isUnsigned ? maxUnsigned - 1 : maxUnsigned / 2 - 1; - const min = isUnsigned ? 0 : -maxUnsigned / 2; + const maxUnsigned = BigInt(2 ** power); + const max = isUnsigned ? maxUnsigned - BigInt(1) : maxUnsigned / BigInt(2) - BigInt(1); + const min = isUnsigned ? BigInt(0) : -maxUnsigned / BigInt(2); return [ min, max ]; }; diff --git a/ui/address/contract/useWatchAccount.tsx b/ui/address/contract/useWatchAccount.tsx index d4035e6ce5..ac19b2f55e 100644 --- a/ui/address/contract/useWatchAccount.tsx +++ b/ui/address/contract/useWatchAccount.tsx @@ -1,24 +1,31 @@ import { watchAccount, getAccount } from '@wagmi/core'; import React from 'react'; +import type { Config } from 'wagmi'; +import { useConfig } from 'wagmi'; -export function getWalletAccount() { +export function getWalletAccount(config: Config) { try { - return getAccount(); + return getAccount(config); } catch (error) { return null; } } export default function useWatchAccount() { - const [ account, setAccount ] = React.useState(getWalletAccount()); + const config = useConfig(); + const [ account, setAccount ] = React.useState(getWalletAccount(config)); React.useEffect(() => { if (!account) { return; } - return watchAccount(setAccount); - }, [ account ]); + return watchAccount(config, { + onChange(account) { + setAccount(account); + }, + }); + }, [ account, config ]); return account; } diff --git a/ui/address/tokenSelect/TokenSelect.pw.tsx b/ui/address/tokenSelect/TokenSelect.pw.tsx index 7a2cbb5fa0..e58975e540 100644 --- a/ui/address/tokenSelect/TokenSelect.pw.tsx +++ b/ui/address/tokenSelect/TokenSelect.pw.tsx @@ -14,6 +14,7 @@ const ASSET_URL = tokenInfoERC20a.icon_url as string; const TOKENS_ERC20_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-20'; const TOKENS_ERC721_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-721'; const TOKENS_ER1155_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-1155'; +const TOKENS_ER404_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-404'; const ADDRESS_API_URL = buildApiUrl('address', { hash: '1' }); const hooksConfig = { router: { @@ -46,6 +47,10 @@ const test = base.extend({ status: 200, body: JSON.stringify(tokensMock.erc1155List), }), { times: 1 }); + await page.route(TOKENS_ER404_API_URL, async(route) => route.fulfill({ + status: 200, + body: JSON.stringify(tokensMock.erc404List), + }), { times: 1 }); use(page); }, @@ -158,6 +163,10 @@ base('long values', async({ mount, page }) => { status: 200, body: JSON.stringify({ items: [ tokensMock.erc1155LongId ] }), }), { times: 1 }); + await page.route(TOKENS_ER404_API_URL, async(route) => route.fulfill({ + status: 200, + body: JSON.stringify(tokensMock.erc404List), + }), { times: 1 }); await mount( diff --git a/ui/address/tokenSelect/TokenSelectItem.tsx b/ui/address/tokenSelect/TokenSelectItem.tsx index 479f632d96..fc7d6cde27 100644 --- a/ui/address/tokenSelect/TokenSelectItem.tsx +++ b/ui/address/tokenSelect/TokenSelectItem.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { route } from 'nextjs-routes'; +import getCurrencyValue from 'lib/getCurrencyValue'; import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import LinkInternal from 'ui/shared/LinkInternal'; import TruncatedValue from 'ui/shared/TruncatedValue'; @@ -45,6 +46,25 @@ const TokenSelectItem = ({ data }: Props) => { ); } + case 'ERC-404': { + return ( + <> + { data.token_id !== null && ( + + #{ data.token_id || 0 } + + ) } + { data.value !== null && ( + + { data.token.decimals ? + getCurrencyValue({ value: data.value, decimals: data.token.decimals, accuracy: 2 }).valueStr : + BigNumber(data.value).toFormat() + } + + ) } + + ); + } } })(); diff --git a/ui/address/tokenSelect/TokenSelectMenu.tsx b/ui/address/tokenSelect/TokenSelectMenu.tsx index d247a0e3cd..22f87950ed 100644 --- a/ui/address/tokenSelect/TokenSelectMenu.tsx +++ b/ui/address/tokenSelect/TokenSelectMenu.tsx @@ -16,12 +16,13 @@ interface Props { searchTerm: string; erc20sort: Sort; erc1155sort: Sort; + erc404sort: Sort; filteredData: FormattedData; onInputChange: (event: ChangeEvent) => void; onSortClick: (event: React.SyntheticEvent) => void; } -const TokenSelectMenu = ({ erc20sort, erc1155sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => { +const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => { const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200'); @@ -43,15 +44,17 @@ const TokenSelectMenu = ({ erc20sort, erc1155sort, filteredData, onInputChange, { Object.entries(filteredData).sort(sortTokenGroups).map(([ tokenType, tokenInfo ]) => { - if (tokenInfo.items.length === 0) { return null; } const type = tokenType as TokenType; - const arrowTransform = (type === 'ERC-1155' && erc1155sort === 'desc') || (type === 'ERC-20' && erc20sort === 'desc') ? - 'rotate(90deg)' : - 'rotate(-90deg)'; + const arrowTransform = + (type === 'ERC-1155' && erc1155sort === 'desc') || + (type === 'ERC-404' && erc404sort === 'desc') || + (type === 'ERC-20' && erc20sort === 'desc') ? + 'rotate(90deg)' : + 'rotate(-90deg)'; const sortDirection: Sort = (() => { switch (type) { case 'ERC-1155': @@ -62,7 +65,10 @@ const TokenSelectMenu = ({ erc20sort, erc1155sort, filteredData, onInputChange, return 'desc'; } })(); - const hasSort = type === 'ERC-1155' || (type === 'ERC-20' && tokenInfo.items.some(({ usd }) => usd)); + const hasSort = + (type === 'ERC-404' && tokenInfo.items.some(item => item.value)) || + type === 'ERC-1155' || + (type === 'ERC-20' && tokenInfo.items.some(({ usd }) => usd)); const numPrefix = tokenInfo.isOverflow ? '>' : ''; return ( diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index 9dba7e43bb..0f13fc0097 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png index 0079d7d3db..dc1db04181 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_dark-color-mode_base-view-dark-mode-2.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png index 6b7fa96751..90b6524154 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png index 464e028a1c..df33a35e69 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_base-view-dark-mode-2.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png index f2151d30c1..97f8b096bc 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_filter-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png index d7d140f036..1f59eeb768 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_long-values-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png index 98ab4ea3a3..924dca0642 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png index c276436451..56f4a24113 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-1.png differ diff --git a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png index 10c6465fa7..0638c45f9e 100644 Binary files a/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png and b/ui/address/tokenSelect/__screenshots__/TokenSelect.pw.tsx_default_sort-2.png differ diff --git a/ui/address/tokenSelect/useTokenSelect.ts b/ui/address/tokenSelect/useTokenSelect.ts index da268d248e..95346e25c3 100644 --- a/ui/address/tokenSelect/useTokenSelect.ts +++ b/ui/address/tokenSelect/useTokenSelect.ts @@ -10,6 +10,7 @@ import { filterTokens } from '../utils/tokenUtils'; export default function useTokenSelect(data: FormattedData) { const [ searchTerm, setSearchTerm ] = React.useState(''); const [ erc1155sort, setErc1155Sort ] = React.useState('desc'); + const [ erc404sort, setErc404Sort ] = React.useState('desc'); const [ erc20sort, setErc20Sort ] = React.useState('desc'); const onInputChange = React.useCallback((event: ChangeEvent) => { @@ -21,6 +22,9 @@ export default function useTokenSelect(data: FormattedData) { if (tokenType === 'ERC-1155') { setErc1155Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc'); } + if (tokenType === 'ERC-404') { + setErc404Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc'); + } if (tokenType === 'ERC-20') { setErc20Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc'); } @@ -37,6 +41,7 @@ export default function useTokenSelect(data: FormattedData) { searchTerm, erc20sort, erc1155sort, + erc404sort, onInputChange, onSortClick, data, diff --git a/ui/address/tokens/AddressCollections.tsx b/ui/address/tokens/AddressCollections.tsx index 2be76c5c87..caf185a30c 100644 --- a/ui/address/tokens/AddressCollections.tsx +++ b/ui/address/tokens/AddressCollections.tsx @@ -64,7 +64,7 @@ const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Pro { + const valueResult = token.decimals && value ? getCurrencyValue({ value, decimals: token.decimals, accuracy: 2 }).valueStr : value; const tokenInstanceLink = tokenInstance.id ? route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenInstance.id } }) : undefined; @@ -31,13 +33,18 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P isLoading={ isLoading } /> - + ID# - - { Number(value) > 1 && Qty { value } } + + { valueResult && ( + + Qty + { valueResult } + + ) } { withTokenLink && ( diff --git a/ui/address/utils/tokenUtils.ts b/ui/address/utils/tokenUtils.ts index 67f2e52c2e..b204166a18 100644 --- a/ui/address/utils/tokenUtils.ts +++ b/ui/address/utils/tokenUtils.ts @@ -22,13 +22,13 @@ export interface TokenSelectDataItem { type TokenGroup = [string, TokenSelectDataItem]; -const TOKEN_GROUPS_ORDER: Array = [ 'ERC-20', 'ERC-721', 'ERC-1155' ]; +const TOKEN_GROUPS_ORDER: Array = [ 'ERC-20', 'ERC-721', 'ERC-1155', 'ERC-404' ]; export const sortTokenGroups = (groupA: TokenGroup, groupB: TokenGroup) => { return TOKEN_GROUPS_ORDER.indexOf(groupA[0] as TokenType) > TOKEN_GROUPS_ORDER.indexOf(groupB[0] as TokenType) ? 1 : -1; }; -const sortErc1155Tokens = (sort: Sort) => (dataA: AddressTokenBalance, dataB: AddressTokenBalance) => { +const sortErc1155or404Tokens = (sort: Sort) => (dataA: AddressTokenBalance, dataB: AddressTokenBalance) => { if (dataA.value === dataB.value) { return 0; } @@ -38,6 +38,7 @@ const sortErc1155Tokens = (sort: Sort) => (dataA: AddressTokenBalance, dataB: Ad return Number(dataA.value) > Number(dataB.value) ? 1 : -1; }; + const sortErc20Tokens = (sort: Sort) => (dataA: TokenEnhancedData, dataB: TokenEnhancedData) => { if (!dataA.usd && !dataB.usd) { return 0; @@ -63,7 +64,8 @@ const sortErc721Tokens = () => () => 0; export const sortingFns = { 'ERC-20': sortErc20Tokens, 'ERC-721': sortErc721Tokens, - 'ERC-1155': sortErc1155Tokens, + 'ERC-1155': sortErc1155or404Tokens, + 'ERC-404': sortErc1155or404Tokens, }; export const filterTokens = (searchTerm: string) => ({ token }: AddressTokenBalance) => { diff --git a/ui/address/utils/useFetchTokens.ts b/ui/address/utils/useFetchTokens.ts index 9b5bcda583..53055d9ced 100644 --- a/ui/address/utils/useFetchTokens.ts +++ b/ui/address/utils/useFetchTokens.ts @@ -36,6 +36,11 @@ export default function useFetchTokens({ hash }: Props) { queryParams: { type: 'ERC-1155' }, queryOptions: { enabled: Boolean(hash), refetchOnMount: false }, }); + const erc404query = useApiQuery('address_tokens', { + pathParams: { hash }, + queryParams: { type: 'ERC-404' }, + queryOptions: { enabled: Boolean(hash), refetchOnMount: false }, + }); const queryClient = useQueryClient(); @@ -78,6 +83,10 @@ export default function useFetchTokens({ hash }: Props) { updateTokensData('ERC-1155', payload); }, [ updateTokensData ]); + const handleTokenBalancesErc404Message: SocketMessage.AddressTokenBalancesErc1155['handler'] = React.useCallback((payload) => { + updateTokensData('ERC-404', payload); + }, [ updateTokensData ]); + const channel = useSocketChannel({ topic: `addresses:${ hash?.toLowerCase() }`, isDisabled: Boolean(hash) && (erc20query.isPlaceholderData || erc721query.isPlaceholderData || erc1155query.isPlaceholderData), @@ -98,6 +107,11 @@ export default function useFetchTokens({ hash }: Props) { event: 'updated_token_balances_erc_1155', handler: handleTokenBalancesErc1155Message, }); + useSocketMessage({ + channel, + event: 'updated_token_balances_erc_404', + handler: handleTokenBalancesErc404Message, + }); const data = React.useMemo(() => { return { @@ -113,12 +127,16 @@ export default function useFetchTokens({ hash }: Props) { items: erc1155query.data?.items.map(calculateUsdValue) || [], isOverflow: Boolean(erc1155query.data?.next_page_params), }, + 'ERC-404': { + items: erc404query.data?.items.map(calculateUsdValue) || [], + isOverflow: Boolean(erc1155query.data?.next_page_params), + }, }; - }, [ erc1155query.data, erc20query.data, erc721query.data ]); + }, [ erc1155query.data, erc20query.data, erc721query.data, erc404query.data ]); return { - isPending: erc20query.isPending || erc721query.isPending || erc1155query.isPending, - isError: erc20query.isError || erc721query.isError || erc1155query.isError, + isPending: erc20query.isPending || erc721query.isPending || erc1155query.isPending || erc404query.isPending, + isError: erc20query.isError || erc721query.isError || erc1155query.isError || erc404query.isError, data, }; } diff --git a/ui/addressVerification/AddressVerificationModal.tsx b/ui/addressVerification/AddressVerificationModal.tsx index b70d472dc7..c81cb10e6c 100644 --- a/ui/addressVerification/AddressVerificationModal.tsx +++ b/ui/addressVerification/AddressVerificationModal.tsx @@ -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 22a80fef28..9db7f2dd97 100644 --- a/ui/addressVerification/steps/AddressVerificationStepSignature.tsx +++ b/ui/addressVerification/steps/AddressVerificationStepSignature.tsx @@ -80,15 +80,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre const onSubmit = handleSubmit(onFormSubmit); - const { signMessage, isLoading: isSigning } = useSignMessage({ - onSuccess: (data) => { - setValue('signature', data); - onSubmit(); - }, - onError: (error) => { - return setError('root', { type: 'SIGNING_FAIL', message: (error as Error)?.message || 'Oops! Something went wrong' }); - }, - }); + const { signMessage, isPending: isSigning } = useSignMessage(); const handleSignMethodChange = React.useCallback((value: typeof signMethod) => { setSignMethod(value); @@ -108,8 +100,16 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre } const message = getValues('message'); - signMessage({ message }); - }, [ clearErrors, isConnected, getValues, signMessage, setError ]); + signMessage({ message }, { + onSuccess: (data) => { + setValue('signature', data); + onSubmit(); + }, + onError: (error) => { + return setError('root', { type: 'SIGNING_FAIL', message: (error as Error)?.message || 'Oops! Something went wrong' }); + }, + }); + }, [ clearErrors, isConnected, getValues, signMessage, setError, setValue, onSubmit ]); const handleManualSignClick = React.useCallback(() => { clearErrors('root'); @@ -219,7 +219,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre { !noWeb3Provider && ( Sign via Web3 wallet - Sign manually + Sign manually ) } { signMethod === 'manual' && } diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index e569f21572..b065cfdb5b 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -5,6 +5,8 @@ import { useRouter } from 'next/router'; import React from 'react'; import { scroller, Element } from 'react-scroll'; +import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; + import { route } from 'nextjs-routes'; import config from 'configs/app'; @@ -19,6 +21,7 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider'; import DetailsTimestamp from 'ui/shared/DetailsTimestamp'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import IconSvg from 'ui/shared/IconSvg'; @@ -27,6 +30,8 @@ import PrevNext from 'ui/shared/PrevNext'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; import TextSeparator from 'ui/shared/TextSeparator'; import Utilization from 'ui/shared/Utilization/Utilization'; +import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; +import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo'; import BlockDetailsBlobInfo from './details/BlockDetailsBlobInfo'; import type { BlockQuery } from './useBlockQuery'; @@ -214,6 +219,31 @@ const BlockDetails = ({ query }: Props) => { ) } + + { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && ( + <> + + { data.zksync.batch_number ? ( + + ) : Pending } + + + + + + ) } + { !config.UI.views.block.hiddenFields?.miner && ( { <> + { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && + } + { !isPlaceholderData && } { data.bitcoin_merged_mining_header && ( diff --git a/ui/home/Stats.tsx b/ui/home/Stats.tsx index 6d14a8a2f4..eb2b49cdd7 100644 --- a/ui/home/Stats.tsx +++ b/ui/home/Stats.tsx @@ -33,10 +33,21 @@ const Stats = () => { }, }); - if (isError || zkEvmLatestBatchQuery.isError) { + const zkSyncLatestBatchQuery = useApiQuery('homepage_zksync_latest_batch', { + queryOptions: { + placeholderData: 12345, + enabled: rollupFeature.isEnabled && rollupFeature.type === 'zkSync', + }, + }); + + if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError) { return null; } + const isLoading = isPlaceholderData || + (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && zkEvmLatestBatchQuery.isPlaceholderData) || + (rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData); + let content; const lastItemTouchStyle = { gridColumn: { base: 'span 2', lg: 'unset' } }; @@ -52,7 +63,7 @@ const Stats = () => { const gasInfoTooltip = hasGasTracker && data.gas_prices ? ( { content = ( <> - { rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' ? ( + { rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && ( - ) : ( + ) } + { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && ( + + ) } + { !(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && ( ) } { hasAvgBlockTime && ( @@ -89,7 +110,7 @@ const Stats = () => { icon="clock-light" title="Average block time" value={ `${ (data.average_block_time / 1000).toFixed(1) }s` } - isLoading={ isPlaceholderData } + isLoading={ isLoading } /> ) } { title="Total transactions" value={ Number(data.total_transactions).toLocaleString() } url={ route({ pathname: '/txs' }) } - isLoading={ isPlaceholderData } + isLoading={ isLoading } /> { hasGasTracker && data.gas_prices && ( { value={ } _last={ isOdd ? lastItemTouchStyle : undefined } tooltip={ gasInfoTooltip } - isLoading={ isPlaceholderData } + isLoading={ isLoading } /> ) } { data.rootstock_locked_btc && ( @@ -122,7 +143,7 @@ const Stats = () => { title="BTC Locked in 2WP" value={ `${ BigNumber(data.rootstock_locked_btc).div(WEI).dp(0).toFormat() } RBTC` } _last={ isOdd ? lastItemTouchStyle : undefined } - isLoading={ isPlaceholderData } + isLoading={ isLoading } /> ) } diff --git a/ui/home/StatsItem.tsx b/ui/home/StatsItem.tsx index f16f49bfd3..fd66393b2d 100644 --- a/ui/home/StatsItem.tsx +++ b/ui/home/StatsItem.tsx @@ -27,7 +27,7 @@ const StatsItem = ({ icon, title, value, className, tooltip, url, isLoading }: P [`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { alignItems: 'center' }, }; - const bgColor = useColorModeValue('blue.50', 'blue.800'); + const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100'); const loadingBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); return ( diff --git a/ui/home/indicators/ChainIndicatorItem.tsx b/ui/home/indicators/ChainIndicatorItem.tsx index 51efc16b23..ae54e4fddc 100644 --- a/ui/home/indicators/ChainIndicatorItem.tsx +++ b/ui/home/indicators/ChainIndicatorItem.tsx @@ -7,18 +7,20 @@ import type { ChainIndicatorId } from 'types/homepage'; import type { ResourceError } from 'lib/api/resources'; import useIsMobile from 'lib/hooks/useIsMobile'; +import IconSvg from 'ui/shared/IconSvg'; interface Props { id: ChainIndicatorId; title: string; value: (stats: HomeStats) => string; + valueDiff?: (stats?: HomeStats) => number | null | undefined; icon: React.ReactNode; isSelected: boolean; onClick: (id: ChainIndicatorId) => void; stats: UseQueryResult>; } -const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats }: Props) => { +const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onClick, stats }: Props) => { const isMobile = useIsMobile(); const activeBgColorDesktop = useColorModeValue('white', 'gray.900'); @@ -53,6 +55,25 @@ const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats return { value(stats.data) }; })(); + const valueDiffContent = (() => { + if (isMobile || !valueDiff) { + return null; + } + const diff = valueDiff(stats.data); + if (diff === undefined || diff === null) { + return null; + } + + const diffColor = diff >= 0 ? 'green.500' : 'red.500'; + + return ( + + + { diff }% + + ); + })(); + return ( { title } - { valueContent } + + { valueContent } + { valueDiffContent } +
); diff --git a/ui/home/indicators/ChainIndicators.tsx b/ui/home/indicators/ChainIndicators.tsx index bafde43c7f..0195d5dc1a 100644 --- a/ui/home/indicators/ChainIndicators.tsx +++ b/ui/home/indicators/ChainIndicators.tsx @@ -1,10 +1,11 @@ -import { Flex, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; +import { Box, Flex, Skeleton, Text, useColorModeValue } 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 Hint from 'ui/shared/Hint'; +import IconSvg from 'ui/shared/IconSvg'; import ChainIndicatorChartContainer from './ChainIndicatorChartContainer'; import ChainIndicatorItem from './ChainIndicatorItem'; @@ -56,19 +57,39 @@ const ChainIndicators = () => { } return ( - + { indicator?.value(statsQueryResult.data) } ); })(); + const valueDiff = (() => { + if (!statsQueryResult.data || !indicator?.valueDiff) { + return null; + } + + const diff = indicator.valueDiff(statsQueryResult.data); + if (diff === undefined || diff === null) { + return null; + } + + const diffColor = diff >= 0 ? 'green.500' : 'red.500'; + + return ( + + + { diff }% + + ); + })(); + return ( { { indicator?.title } { indicator?.hint && } - { valueTitle } + + { valueTitle } + { valueDiff } + { indicators.length > 1 && ( diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png index a7a5040e67..f0bd10b718 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png differ diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png index 040ad7ab5a..24d453ca53 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png differ diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png index 7ed3cb098b..671945380b 100644 Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png differ diff --git a/ui/home/indicators/types.ts b/ui/home/indicators/types.ts index 86e7268f78..7ae2453ada 100644 --- a/ui/home/indicators/types.ts +++ b/ui/home/indicators/types.ts @@ -10,6 +10,7 @@ export interface TChainIndicator { id: ChainIndicatorId; title: string; value: (stats: HomeStats) => string; + valueDiff?: (stats?: HomeStats) => number | null | undefined; icon: React.ReactNode; hint?: string; api: { diff --git a/ui/home/indicators/utils/indicators.tsx b/ui/home/indicators/utils/indicators.tsx index 6322933693..62783b7bee 100644 --- a/ui/home/indicators/utils/indicators.tsx +++ b/ui/home/indicators/utils/indicators.tsx @@ -54,6 +54,7 @@ const coinPriceIndicator: TChainIndicator<'stats_charts_market'> = { value: (stats) => stats.coin_price === null ? '$N/A' : '$' + Number(stats.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }), + valueDiff: (stats) => stats?.coin_price !== null ? stats?.coin_price_change_percentage : null, icon: , hint: `${ config.chain.governanceToken.symbol || config.chain.currency.symbol } token daily price in USD.`, api: { diff --git a/ui/marketplace/AppSecurityReport.tsx b/ui/marketplace/AppSecurityReport.tsx new file mode 100644 index 0000000000..35301af312 --- /dev/null +++ b/ui/marketplace/AppSecurityReport.tsx @@ -0,0 +1,82 @@ +import { Box, Text, Link, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import type { MarketplaceAppSecurityReport } from 'types/client/marketplace'; + +import config from 'configs/app'; +import { apos } from 'lib/html-entities'; +import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; +import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; +import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; +import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; + +type Props = { + id: string; + securityReport?: MarketplaceAppSecurityReport; + height?: string | undefined; + showContractList: () => void; + isLoading?: boolean; + onlyIcon?: boolean; + source: 'Security view' | 'App modal' | 'App page'; +} + +const AppSecurityReport = ({ id, securityReport, height, showContractList, isLoading, onlyIcon, source }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const handleButtonClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Security score', Info: id, Source: source }); + onToggle(); + }, [ id, source, onToggle ]); + + const handleLinkClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Analyzed contracts', Info: id, Source: 'Security score popup' }); + showContractList(); + }, [ id, showContractList ]); + + if (!securityReport && !isLoading) { + return null; + } + + const { + securityScore = 0, + solidityScanContractsNumber = 0, + issueSeverityDistribution = {} as MarketplaceAppSecurityReport['overallInfo']['issueSeverityDistribution'], + totalIssues = 0, + } = securityReport?.overallInfo || {}; + + return ( + + + + + + + + { solidityScanContractsNumber } smart contract{ solidityScanContractsNumber === 1 ? ' was' : 's were' } evaluated to determine + this protocol{ apos }s overall security score on the { config.chain.name } network. + + + { issueSeverityDistribution && totalIssues > 0 && ( + + Threat score & vulnerabilities + + + ) } + + Analyzed contracts + + + + + + ); +}; + +export default AppSecurityReport; diff --git a/ui/marketplace/ContractListButton.tsx b/ui/marketplace/ContractListButton.tsx new file mode 100644 index 0000000000..6a8bfce05e --- /dev/null +++ b/ui/marketplace/ContractListButton.tsx @@ -0,0 +1,66 @@ +import { Link, Tooltip, Skeleton } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +import config from 'configs/app'; +import IconSvg from 'ui/shared/IconSvg'; + +export enum ContractListButtonVariants { + ALL_CONTRACTS = 'all contracts', + VERIFIED_CONTRACTS = 'verified contracts', +} + +const values = { + [ContractListButtonVariants.ALL_CONTRACTS]: { + icon: 'contracts' as const, + iconColor: 'gray.500', + tooltip: `Total number of contracts deployed by the protocol on ${ config.chain.name }`, + }, + [ContractListButtonVariants.VERIFIED_CONTRACTS]: { + icon: 'contracts_verified' as const, + iconColor: 'green.500', + tooltip: `Number of verified contracts on ${ config.chain.name }`, + }, +}; + +interface Props { + children: string | number; + onClick: (event: MouseEvent) => void; + variant: ContractListButtonVariants; + isLoading?: boolean; +} + +const ContractListButton = ({ children, onClick, variant, isLoading }: Props) => { + const { icon, iconColor, tooltip } = values[variant]; + return ( + + + + { icon && } + { children } + + + + ); +}; + +export default ContractListButton; diff --git a/ui/marketplace/ContractListModal.tsx b/ui/marketplace/ContractListModal.tsx new file mode 100644 index 0000000000..090c73891e --- /dev/null +++ b/ui/marketplace/ContractListModal.tsx @@ -0,0 +1,119 @@ +import { + Box, Modal, Text, ModalBody, + ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, +} from '@chakra-ui/react'; +import React from 'react'; + +import type { MarketplaceAppSecurityReport } from 'types/client/marketplace'; +import { ContractListTypes } from 'types/client/marketplace'; + +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'; + +import ContractSecurityReport from './ContractSecurityReport'; + +type Props = { + onClose: () => void; + onBack?: () => void; + type: ContractListTypes; + contracts?: MarketplaceAppSecurityReport['contractsData']; +} + +const titles = { + [ContractListTypes.ALL]: `All app${ apos }s smart contracts`, + [ContractListTypes.ANALYZED]: 'Analyzed contracts', + [ContractListTypes.VERIFIED]: 'Verified contracts', +}; + +const ContractListModal = ({ onClose, onBack, type, contracts }: Props) => { + const isMobile = useIsMobile(); + + const displayedContracts = React.useMemo(() => { + if (!contracts) { + return []; + } + switch (type) { + default: + case ContractListTypes.ALL: + return contracts; + case ContractListTypes.ANALYZED: + return contracts + .filter((contract) => Boolean(contract.solidityScanReport)) + .sort((a, b) => + (parseFloat(b.solidityScanReport?.scan_summary.score_v2 ?? '0')) - (parseFloat(a.solidityScanReport?.scan_summary.score_v2 ?? '0')), + ); + case ContractListTypes.VERIFIED: + return contracts.filter((contract) => contract.isVerified); + } + }, [ contracts, type ]); + + if (!contracts) { + return null; + } + + return ( + + + + + { onBack && ( + + ) } + + { titles[type] } + + + + + { displayedContracts.map((contract) => ( + + { type === ContractListTypes.ANALYZED && ( + + + + ) } + + + )) } + + + + ); +}; + +export default ContractListModal; diff --git a/ui/marketplace/ContractSecurityReport.tsx b/ui/marketplace/ContractSecurityReport.tsx new file mode 100644 index 0000000000..42e28b06dd --- /dev/null +++ b/ui/marketplace/ContractSecurityReport.tsx @@ -0,0 +1,64 @@ +import { Box, Text, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import type { SolidityscanReport } from 'types/api/contract'; + +import config from 'configs/app'; +import * as mixpanel from 'lib/mixpanel/index'; +import LinkExternal from 'ui/shared/LinkExternal'; +import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; +import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; +import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; + +type Props = { + securityReport?: SolidityscanReport['scan_report'] | null; +} + +const ContractSecurityReport = ({ securityReport }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const handleClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Security score', Source: 'Analyzed contracts popup' }); + onToggle(); + }, [ onToggle ]); + + if (!securityReport) { + return null; + } + + const url = securityReport?.scanner_reference_url; + const { + score_v2: securityScore, + issue_severity_distribution: issueSeverityDistribution, + } = securityReport.scan_summary; + + const totalIssues = Object.values(issueSeverityDistribution as Record).reduce((acc, val) => acc + val, 0); + + return ( + + + + + + + + The security score was derived from evaluating the smart contracts of a protocol on the { config.chain.name } network. + + + { issueSeverityDistribution && totalIssues > 0 && ( + + Threat score & vulnerabilities + + + ) } + View full report + + + + ); +}; + +export default ContractSecurityReport; diff --git a/ui/marketplace/EmptySearchResult.tsx b/ui/marketplace/EmptySearchResult.tsx new file mode 100644 index 0000000000..d3ccf59701 --- /dev/null +++ b/ui/marketplace/EmptySearchResult.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +import { MarketplaceCategory } from 'types/client/marketplace'; + +import config from 'configs/app'; +import { apos } from 'lib/html-entities'; +import EmptySearchResultDefault from 'ui/shared/EmptySearchResult'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkExternal from 'ui/shared/LinkExternal'; + +const feature = config.features.marketplace; + +type Props = { + favoriteApps: Array; + selectedCategoryId?: string; +} + +const EmptySearchResult = ({ favoriteApps, selectedCategoryId }: Props) => ( + + You don{ apos }t have any favorite apps. + Click on the icon on the app{ apos }s card to add it to Favorites. + + ) : ( + <> + No matching apps found. + { 'suggestIdeasFormUrl' in feature && ( + <> + { ' ' }Have a groundbreaking idea or app suggestion?{ ' ' } + Share it with us + + ) } + + ) + } + /> +); + +export default React.memo(EmptySearchResult); diff --git a/ui/marketplace/MarketplaceAppCard.tsx b/ui/marketplace/MarketplaceAppCard.tsx index 223ce5808f..e387cb0809 100644 --- a/ui/marketplace/MarketplaceAppCard.tsx +++ b/ui/marketplace/MarketplaceAppCard.tsx @@ -1,21 +1,21 @@ -import { Box, IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, Tooltip } from '@chakra-ui/react'; +import { Box, IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue } from '@chakra-ui/react'; import type { MouseEvent } from 'react'; import React, { useCallback } from 'react'; import type { MarketplaceAppPreview } from 'types/client/marketplace'; import * as mixpanel from 'lib/mixpanel/index'; -import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; import MarketplaceAppCardLink from './MarketplaceAppCardLink'; +import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon'; interface Props extends MarketplaceAppPreview { onInfoClick: (id: string) => void; isFavorite: boolean; - onFavoriteClick: (id: string, isFavorite: boolean) => void; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'Discovery view') => void; isLoading: boolean; - showDisclaimer: (id: string) => void; + onAppClick: (event: MouseEvent, id: string) => void; } const MarketplaceAppCard = ({ @@ -31,48 +31,23 @@ const MarketplaceAppCard = ({ isFavorite, onFavoriteClick, isLoading, - showDisclaimer, internalWallet, + onAppClick, }: Props) => { const categoriesLabel = categories.join(', '); - const handleClick = useCallback((event: MouseEvent) => { - const isShown = window.localStorage.getItem('marketplace-disclaimer-shown'); - if (!isShown) { - event.preventDefault(); - showDisclaimer(id); - } - }, [ showDisclaimer, id ]); - const handleInfoClick = useCallback((event: MouseEvent) => { event.preventDefault(); - mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id }); + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Discovery view' }); onInfoClick(id); }, [ onInfoClick, id ]); const handleFavoriteClick = useCallback(() => { - onFavoriteClick(id, isFavorite); + onFavoriteClick(id, isFavorite, 'Discovery view'); }, [ onFavoriteClick, id, isFavorite ]); const logoUrl = useColorModeValue(logo, logoDarkMode || logo); - const [ integrationIcon, integrationIconColor, integrationText ] = React.useMemo(() => { - let icon: IconName = 'integration/partial'; - let color = 'gray.400'; - let text = 'This app opens in Blockscout without Blockscout wallet functionality. Use your external web3 wallet to connect directly to this application'; - - if (external) { - icon = 'arrows/north-east'; - text = 'This app opens in a separate tab'; - } else if (internalWallet) { - icon = 'integration/full'; - color = 'green.500'; - text = 'This app opens in Blockscout and your Blockscout wallet connects automatically'; - } - - return [ icon, color, text ]; - }, [ external, internalWallet ]); - return ( - - - + : - + : + } /> ) } diff --git a/ui/marketplace/MarketplaceAppCardLink.tsx b/ui/marketplace/MarketplaceAppCardLink.tsx index 907a0db8c1..6dd5853cd8 100644 --- a/ui/marketplace/MarketplaceAppCardLink.tsx +++ b/ui/marketplace/MarketplaceAppCardLink.tsx @@ -8,17 +8,21 @@ type Props = { url: string; external?: boolean; title: string; - onClick?: (event: MouseEvent) => void; + onClick?: (event: MouseEvent, id: string) => void; } const MarketplaceAppCardLink = ({ url, external, id, title, onClick }: Props) => { + const handleClick = React.useCallback((event: MouseEvent) => { + onClick?.(event, id); + }, [ onClick, id ]); + return external ? ( { title } ) : ( - + { title } diff --git a/ui/marketplace/MarketplaceAppInfo.pw.tsx b/ui/marketplace/MarketplaceAppInfo.pw.tsx index 4e0a627642..3419aaa5ec 100644 --- a/ui/marketplace/MarketplaceAppInfo.pw.tsx +++ b/ui/marketplace/MarketplaceAppInfo.pw.tsx @@ -28,7 +28,7 @@ test.describe('mobile', () => {
, ); - await page.getByText('Info').click(); + await page.getByLabel('Show project info').click(); await expect(page).toHaveScreenshot(); }); diff --git a/ui/marketplace/MarketplaceAppInfo.tsx b/ui/marketplace/MarketplaceAppInfo.tsx index 5b25aea641..00646e3291 100644 --- a/ui/marketplace/MarketplaceAppInfo.tsx +++ b/ui/marketplace/MarketplaceAppInfo.tsx @@ -22,7 +22,7 @@ const MarketplaceAppInfo = ({ data }: Props) => { if (isMobile) { return ( <> - + diff --git a/ui/marketplace/MarketplaceAppInfo/Content.tsx b/ui/marketplace/MarketplaceAppInfo/Content.tsx index 04b55b84b4..778d421237 100644 --- a/ui/marketplace/MarketplaceAppInfo/Content.tsx +++ b/ui/marketplace/MarketplaceAppInfo/Content.tsx @@ -13,7 +13,7 @@ interface Props { const SOCIAL_LINKS: Array> = [ { field: 'github', icon: 'social/github_filled', title: 'Github' }, - { field: 'twitter', icon: 'social/twitter_filled', title: 'Twitter' }, + { field: 'twitter', icon: 'social/twitter_filled', title: 'X (ex-Twitter)' }, { field: 'telegram', icon: 'social/telegram_filled', title: 'Telegram' }, { field: 'discord', icon: 'social/discord_filled', title: 'Discord' }, ]; diff --git a/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx b/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx index cef2f9c31e..1f3f6371da 100644 --- a/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx +++ b/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx @@ -5,9 +5,10 @@ import IconSvg from 'ui/shared/IconSvg'; interface Props { onClick: () => void; + onlyIcon?: boolean; } -const TriggerButton = ({ onClick }: Props, ref: React.ForwardedRef) => { +const TriggerButton = ({ onClick, onlyIcon }: Props, ref: React.ForwardedRef) => { return ( ); }; diff --git a/ui/marketplace/MarketplaceAppIntegrationIcon.tsx b/ui/marketplace/MarketplaceAppIntegrationIcon.tsx new file mode 100644 index 0000000000..8ec443c35d --- /dev/null +++ b/ui/marketplace/MarketplaceAppIntegrationIcon.tsx @@ -0,0 +1,51 @@ +import { Tooltip } from '@chakra-ui/react'; +import React from 'react'; + +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + +type Props = { + internalWallet: boolean | undefined; + external: boolean | undefined; +} + +const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => { + const [ icon, iconColor, text ] = React.useMemo(() => { + let icon: IconName = 'integration/partial'; + let color = 'gray.400'; + let text = 'This app opens in Blockscout without Blockscout wallet functionality. Use your external web3 wallet to connect directly to this application'; + + if (external) { + icon = 'arrows/north-east'; + text = 'This app opens in a separate tab'; + } else if (internalWallet) { + icon = 'integration/full'; + color = 'green.500'; + text = 'This app opens in Blockscout and your Blockscout wallet connects automatically'; + } + + return [ icon, color, text ]; + }, [ external, internalWallet ]); + + return ( + + + + ); +}; + +export default MarketplaceAppIntegrationIcon; diff --git a/ui/marketplace/MarketplaceAppModal.pw.tsx b/ui/marketplace/MarketplaceAppModal.pw.tsx index ce0bb7a521..ba6aca1233 100644 --- a/ui/marketplace/MarketplaceAppModal.pw.tsx +++ b/ui/marketplace/MarketplaceAppModal.pw.tsx @@ -1,6 +1,8 @@ import { test, expect, devices } from '@playwright/experimental-ct-react'; import React from 'react'; +import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'; + import { apps as appsMock } from 'mocks/apps/apps'; import TestApp from 'playwright/TestApp'; @@ -9,7 +11,8 @@ import MarketplaceAppModal from './MarketplaceAppModal'; const props = { onClose: () => {}, onFavoriteClick: () => {}, - data: appsMock[0], + showContractList: () => {}, + data: appsMock[0] as MarketplaceAppWithSecurityReport, isFavorite: false, }; diff --git a/ui/marketplace/MarketplaceAppModal.tsx b/ui/marketplace/MarketplaceAppModal.tsx index 297bcae1c3..1bf7c9f916 100644 --- a/ui/marketplace/MarketplaceAppModal.tsx +++ b/ui/marketplace/MarketplaceAppModal.tsx @@ -1,23 +1,29 @@ import { Box, Flex, Heading, IconButton, Image, Link, List, Modal, ModalBody, - ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Tag, Text, useColorModeValue, + ModalCloseButton, ModalContent, ModalFooter, ModalOverlay, Tag, Text, useColorModeValue, } from '@chakra-ui/react'; import React, { useCallback } from 'react'; -import type { MarketplaceAppOverview } from 'types/client/marketplace'; +import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'; +import { ContractListTypes } from 'types/client/marketplace'; +import useFeatureValue from 'lib/growthbook/useFeatureValue'; import useIsMobile from 'lib/hooks/useIsMobile'; import { nbsp } from 'lib/html-entities'; +import * as mixpanel from 'lib/mixpanel/index'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; +import AppSecurityReport from './AppSecurityReport'; +import ContractListButton, { ContractListButtonVariants } from './ContractListButton'; import MarketplaceAppModalLink from './MarketplaceAppModalLink'; type Props = { onClose: () => void; isFavorite: boolean; - onFavoriteClick: (id: string, isFavorite: boolean) => void; - data: MarketplaceAppOverview; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'App modal') => void; + data: MarketplaceAppWithSecurityReport; + showContractList: (id: string, type: ContractListTypes, hasPreviousStep: boolean) => void; } const MarketplaceAppModal = ({ @@ -25,8 +31,13 @@ const MarketplaceAppModal = ({ isFavorite, onFavoriteClick, data, + showContractList: showContractListProp, }: Props) => { + const { value: isExperiment } = useFeatureValue('security_score_exp', false); + const starOutlineIconColor = useColorModeValue('gray.600', 'gray.300'); + const { + id, title, url, external, @@ -39,30 +50,50 @@ const MarketplaceAppModal = ({ logo, logoDarkMode, categories, + securityReport, } = data; const socialLinks = [ telegram ? { - icon: 'social/telega' as IconName, + icon: 'social/telegram_filled' as IconName, url: telegram, } : null, twitter ? { - icon: 'social/tweet' as IconName, + icon: 'social/twitter_filled' as IconName, url: twitter, } : null, ].filter(Boolean); if (github) { if (Array.isArray(github)) { - github.forEach((url) => socialLinks.push({ icon: 'social/git', url })); + github.forEach((url) => socialLinks.push({ icon: 'social/github_filled', url })); } else { - socialLinks.push({ icon: 'social/git', url: github }); + socialLinks.push({ icon: 'social/github_filled', url: github }); } } const handleFavoriteClick = useCallback(() => { - onFavoriteClick(data.id, isFavorite); - }, [ onFavoriteClick, data.id, isFavorite ]); + onFavoriteClick(id, isFavorite, 'App modal'); + }, [ onFavoriteClick, id, isFavorite ]); + + const showContractList = useCallback((type: ContractListTypes) => { + onClose(); + showContractListProp(id, type, true); + }, [ onClose, showContractListProp, id ]); + + const showAllContracts = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Total contracts', Info: id, Source: 'App modal' }); + showContractList(ContractListTypes.ALL); + }, [ showContractList, id ]); + + const showVerifiedContracts = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Verified contracts', Info: id, Source: 'App modal' }); + showContractList(ContractListTypes.VERIFIED); + }, [ showContractList, id ]); + + const showAnalyzedContracts = React.useCallback(() => { + showContractList(ContractListTypes.ANALYZED); + }, [ showContractList ]); const isMobile = useIsMobile(); const logoUrl = useColorModeValue(logo, logoDarkMode || logo); @@ -77,10 +108,11 @@ const MarketplaceAppModal = ({ - - - + + + - : - } - /> - + : + } + /> + + + { (isExperiment && securityReport) && ( + + + + { securityReport.overallInfo.totalContractsNumber } + + + { securityReport.overallInfo.verifiedNumber } + + + ) } + - + @@ -232,6 +289,7 @@ const MarketplaceAppModal = ({ w="20px" h="20px" display="block" + color="text_secondary" /> )) } diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index a3c15e9192..0b82faffeb 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -1,15 +1,20 @@ -import { chakra, Flex, Tooltip, Skeleton } from '@chakra-ui/react'; +import { chakra, Flex, Tooltip, Skeleton, useBoolean, Box } from '@chakra-ui/react'; import React from 'react'; -import type { MarketplaceAppOverview } from 'types/client/marketplace'; +import type { MarketplaceAppOverview, MarketplaceAppSecurityReport } from 'types/client/marketplace'; +import { ContractListTypes } from 'types/client/marketplace'; import { route } from 'nextjs-routes'; import { useAppContext } from 'lib/contexts/app'; +import useFeatureValue from 'lib/growthbook/useFeatureValue'; +import useIsMobile from 'lib/hooks/useIsMobile'; import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; import LinkInternal from 'ui/shared/LinkInternal'; +import AppSecurityReport from './AppSecurityReport'; +import ContractListModal from './ContractListModal'; import MarketplaceAppAlert from './MarketplaceAppAlert'; import MarketplaceAppInfo from './MarketplaceAppInfo'; @@ -17,10 +22,14 @@ type Props = { data: MarketplaceAppOverview | undefined; isLoading: boolean; isWalletConnected: boolean; + securityReport?: MarketplaceAppSecurityReport; } -const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected }: Props) => { +const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityReport }: Props) => { + const [ showContractList, setShowContractList ] = useBoolean(false); const appProps = useAppContext(); + const isMobile = useIsMobile(); + const { value: isExperiment } = useFeatureValue('security_score_exp', false); const goBackUrl = React.useMemo(() => { if (appProps.referrer && appProps.referrer.includes('/apps') && !appProps.referrer.includes('/apps/')) { @@ -36,34 +45,55 @@ const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected }: Props) => } return ( - - - - - - - - - - - - - - - { getHostname(data?.url) } - - - + <> + + + + + + + + + + + + + { (isExperiment && (securityReport || isLoading)) && ( + + + + ) } + + + { getHostname(data?.url) } + + + + { showContractList && ( + + ) } + ); }; diff --git a/ui/marketplace/MarketplaceList.tsx b/ui/marketplace/MarketplaceList.tsx index 2d491383e2..e9fb933919 100644 --- a/ui/marketplace/MarketplaceList.tsx +++ b/ui/marketplace/MarketplaceList.tsx @@ -1,26 +1,23 @@ import { Grid } from '@chakra-ui/react'; import React from 'react'; +import type { MouseEvent } from 'react'; import type { MarketplaceAppPreview } from 'types/client/marketplace'; -import { MarketplaceCategory } from 'types/client/marketplace'; - -import { apos } from 'lib/html-entities'; -import EmptySearchResult from 'ui/shared/EmptySearchResult'; -import IconSvg from 'ui/shared/IconSvg'; +import EmptySearchResult from './EmptySearchResult'; import MarketplaceAppCard from './MarketplaceAppCard'; type Props = { apps: Array; - onAppClick: (id: string) => void; + showAppInfo: (id: string) => void; favoriteApps: Array; - onFavoriteClick: (id: string, isFavorite: boolean) => void; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'Discovery view') => void; isLoading: boolean; - showDisclaimer: (id: string) => void; selectedCategoryId?: string; + onAppClick: (event: MouseEvent, id: string) => void; } -const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLoading, showDisclaimer, selectedCategoryId }: Props) => { +const MarketplaceList = ({ apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId, onAppClick }: Props) => { return apps.length > 0 ? ( ( )) } ) : ( - - You don{ apos }t have any favorite apps. - Click on the icon on the app{ apos }s card to add it to Favorites. - - ) : ( - `Couldn${ apos }t find an app that matches your filter query.` - ) - } - /> + ); }; diff --git a/ui/marketplace/MarketplaceListWithScores.tsx b/ui/marketplace/MarketplaceListWithScores.tsx new file mode 100644 index 0000000000..6d86eb63ba --- /dev/null +++ b/ui/marketplace/MarketplaceListWithScores.tsx @@ -0,0 +1,86 @@ +import { Hide, Show } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +import type { MarketplaceAppWithSecurityReport, ContractListTypes } from 'types/client/marketplace'; + +import DataListDisplay from 'ui/shared/DataListDisplay'; + +import EmptySearchResult from './EmptySearchResult'; +import ListItem from './MarketplaceListWithScores/ListItem'; +import Table from './MarketplaceListWithScores/Table'; + +interface Props { + apps: Array; + showAppInfo: (id: string) => void; + favoriteApps: Array; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; + isLoading: boolean; + selectedCategoryId?: string; + onAppClick: (event: MouseEvent, id: string) => void; + showContractList: (id: string, type: ContractListTypes) => void; +} + +const MarketplaceListWithScores = ({ + apps, + showAppInfo, + favoriteApps, + onFavoriteClick, + isLoading, + selectedCategoryId, + onAppClick, + showContractList, +}: Props) => { + + const displayedApps = React.useMemo(() => apps.sort((a, b) => { + if (!a.securityReport) { + return 1; + } else if (!b.securityReport) { + return -1; + } + return b.securityReport.overallInfo.securityScore - a.securityReport.overallInfo.securityScore; + }), [ apps ]); + + const content = apps.length > 0 ? ( + <> + + { displayedApps.map((app, index) => ( + + )) } + + + + + + ) : null; + + return apps.length > 0 ? ( + + ) : ( + + ); +}; + +export default MarketplaceListWithScores; diff --git a/ui/marketplace/MarketplaceListWithScores/AppLink.tsx b/ui/marketplace/MarketplaceListWithScores/AppLink.tsx new file mode 100644 index 0000000000..bf1b1d6af7 --- /dev/null +++ b/ui/marketplace/MarketplaceListWithScores/AppLink.tsx @@ -0,0 +1,73 @@ +import { Flex, Skeleton, LinkBox, Image, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +import type { MarketplaceAppPreview } from 'types/client/marketplace'; + +import MarketplaceAppCardLink from '../MarketplaceAppCardLink'; +import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon'; + +interface Props { + app: MarketplaceAppPreview; + isLoading: boolean | undefined; + onAppClick: (event: MouseEvent, id: string) => void; + isLarge?: boolean; +} + +const AppLink = ({ app, isLoading, onAppClick, isLarge = false }: Props) => { + const { id, url, external, title, logo, logoDarkMode, internalWallet, categories } = app; + + const logoUrl = useColorModeValue(logo, logoDarkMode || logo); + + const categoriesLabel = categories.join(', '); + + return ( + + + + + + + + + + + + { categoriesLabel } + + + + ); +}; + +export default AppLink; diff --git a/ui/marketplace/MarketplaceListWithScores/ListItem.tsx b/ui/marketplace/MarketplaceListWithScores/ListItem.tsx new file mode 100644 index 0000000000..4b5a8988f4 --- /dev/null +++ b/ui/marketplace/MarketplaceListWithScores/ListItem.tsx @@ -0,0 +1,126 @@ +import { Flex, IconButton, Text } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'; +import { ContractListTypes } from 'types/client/marketplace'; + +import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; +import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; + +import AppSecurityReport from '../AppSecurityReport'; +import ContractListButton, { ContractListButtonVariants } from '../ContractListButton'; +import AppLink from './AppLink'; +import MoreInfoButton from './MoreInfoButton'; + +type Props = { + app: MarketplaceAppWithSecurityReport; + onInfoClick: (id: string) => void; + isFavorite: boolean; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; + isLoading: boolean; + onAppClick: (event: MouseEvent, id: string) => void; + showContractList: (id: string, type: ContractListTypes) => void; +} + +const ListItem = ({ app, onInfoClick, isFavorite, onFavoriteClick, isLoading, onAppClick, showContractList }: Props) => { + const { id, securityReport } = app; + + const handleInfoClick = React.useCallback((event: MouseEvent) => { + event.preventDefault(); + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Security view' }); + onInfoClick(id); + }, [ onInfoClick, id ]); + + const handleFavoriteClick = React.useCallback(() => { + onFavoriteClick(id, isFavorite, 'Security view'); + }, [ onFavoriteClick, id, isFavorite ]); + + const showAllContracts = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Total contracts', Info: id, Source: 'Security view' }); + showContractList(id, ContractListTypes.ALL); + }, [ showContractList, id ]); + + const showVerifiedContracts = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Verified contracts', Info: id, Source: 'Security view' }); + showContractList(id, ContractListTypes.VERIFIED); + }, [ showContractList, id ]); + + const showAnalyzedContracts = React.useCallback(() => { + showContractList(id, ContractListTypes.ANALYZED); + }, [ showContractList, id ]); + + return ( + + + + + { !isLoading && ( + : + + } + /> + ) } + + + + { (securityReport || isLoading) ? ( + <> + + + { securityReport?.overallInfo.totalContractsNumber ?? 0 } + + + { securityReport?.overallInfo.verifiedNumber ?? 0 } + + + ) : ( + Data will be available soon + ) } + + + + + + ); +}; + +export default ListItem; diff --git a/ui/marketplace/MarketplaceListWithScores/MoreInfoButton.tsx b/ui/marketplace/MarketplaceListWithScores/MoreInfoButton.tsx new file mode 100644 index 0000000000..d96bddb66c --- /dev/null +++ b/ui/marketplace/MarketplaceListWithScores/MoreInfoButton.tsx @@ -0,0 +1,29 @@ +import { Link, Skeleton } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +interface Props { + onClick: (event: MouseEvent) => void; + isLoading?: boolean; +} + +const MoreInfoButton = ({ onClick, isLoading }: Props) => ( + + + More info + + +); + +export default MoreInfoButton; diff --git a/ui/marketplace/MarketplaceListWithScores/Table.tsx b/ui/marketplace/MarketplaceListWithScores/Table.tsx new file mode 100644 index 0000000000..1e8cbddf56 --- /dev/null +++ b/ui/marketplace/MarketplaceListWithScores/Table.tsx @@ -0,0 +1,52 @@ +import { Table as ChakraTable, Tbody, Th, Tr } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +import type { MarketplaceAppWithSecurityReport, ContractListTypes } from 'types/client/marketplace'; + +import { default as Thead } from 'ui/shared/TheadSticky'; + +import TableItem from './TableItem'; + +type Props = { + apps: Array; + isLoading?: boolean; + favoriteApps: Array; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; + onAppClick: (event: MouseEvent, id: string) => void; + onInfoClick: (id: string) => void; + showContractList: (id: string, type: ContractListTypes) => void; +} + +const Table = ({ apps, isLoading, favoriteApps, onFavoriteClick, onAppClick, onInfoClick, showContractList }: Props) => { + return ( + + + + + + + + + + + + + { apps.map((app, index) => ( + + )) } + + + ); +}; + +export default Table; diff --git a/ui/marketplace/MarketplaceListWithScores/TableItem.tsx b/ui/marketplace/MarketplaceListWithScores/TableItem.tsx new file mode 100644 index 0000000000..c77b5c5c78 --- /dev/null +++ b/ui/marketplace/MarketplaceListWithScores/TableItem.tsx @@ -0,0 +1,126 @@ +import { Td, Tr, IconButton, Skeleton, Text } from '@chakra-ui/react'; +import React from 'react'; +import type { MouseEvent } from 'react'; + +import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'; +import { ContractListTypes } from 'types/client/marketplace'; + +import * as mixpanel from 'lib/mixpanel/index'; +import IconSvg from 'ui/shared/IconSvg'; + +import AppSecurityReport from '../AppSecurityReport'; +import ContractListButton, { ContractListButtonVariants } from '../ContractListButton'; +import AppLink from './AppLink'; +import MoreInfoButton from './MoreInfoButton'; + +type Props = { + app: MarketplaceAppWithSecurityReport; + isLoading?: boolean; + isFavorite: boolean; + onFavoriteClick: (id: string, isFavorite: boolean, source: 'Security view') => void; + onAppClick: (event: MouseEvent, id: string) => void; + onInfoClick: (id: string) => void; + showContractList: (id: string, type: ContractListTypes) => void; +} + +const TableItem = ({ + app, + isLoading, + isFavorite, + onFavoriteClick, + onAppClick, + onInfoClick, + showContractList, +}: Props) => { + + const { id, securityReport } = app; + + const handleInfoClick = React.useCallback((event: MouseEvent) => { + event.preventDefault(); + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Security view' }); + onInfoClick(id); + }, [ onInfoClick, id ]); + + const handleFavoriteClick = React.useCallback(() => { + onFavoriteClick(id, isFavorite, 'Security view'); + }, [ onFavoriteClick, id, isFavorite ]); + + const showAllContracts = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Total contracts', Info: id, Source: 'Security view' }); + showContractList(id, ContractListTypes.ALL); + }, [ showContractList, id ]); + + const showVerifiedContracts = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Verified contracts', Info: id, Source: 'Security view' }); + showContractList(id, ContractListTypes.VERIFIED); + }, [ showContractList, id ]); + + const showAnalyzedContracts = React.useCallback(() => { + showContractList(id, ContractListTypes.ANALYZED); + }, [ showContractList, id ]); + + return ( + + + + { (securityReport || isLoading) ? ( + <> + + + + + ) : ( + + ) } + + + ); +}; + +export default TableItem; diff --git a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index c94b12f754..99eb6c1d23 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png index 92f7b3afe2..03091af10b 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png index d4a9242e2b..7bb7f5597c 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppInfo.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png index 88130b67fb..8cdc8c9339 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png index cccc4d91be..9e5ef65ca0 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png index 00da3910b5..7422a68ff2 100644 Binary files a/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png and b/ui/marketplace/__screenshots__/MarketplaceAppModal.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/marketplace/useMarketplace.tsx b/ui/marketplace/useMarketplace.tsx index 20cfc8424e..cbf635478d 100644 --- a/ui/marketplace/useMarketplace.tsx +++ b/ui/marketplace/useMarketplace.tsx @@ -2,7 +2,8 @@ import _pickBy from 'lodash/pickBy'; import { useRouter } from 'next/router'; import React from 'react'; -import { MarketplaceCategory } from 'types/client/marketplace'; +import type { ContractListTypes } from 'types/client/marketplace'; +import { MarketplaceCategory, MarketplaceDisplayType } from 'types/client/marketplace'; import useDebounce from 'lib/hooks/useDebounce'; import * as mixpanel from 'lib/mixpanel/index'; @@ -25,17 +26,25 @@ export default function useMarketplace() { const router = useRouter(); const defaultCategoryId = getQueryParamString(router.query.category); const defaultFilterQuery = getQueryParamString(router.query.filter); + const defaultDisplayType = getQueryParamString(router.query.tab); const [ selectedAppId, setSelectedAppId ] = React.useState(null); const [ selectedCategoryId, setSelectedCategoryId ] = React.useState(MarketplaceCategory.ALL); + const [ selectedDisplayType, setSelectedDisplayType ] = React.useState( + Object.values(MarketplaceDisplayType).includes(defaultDisplayType as MarketplaceDisplayType) ? + defaultDisplayType : + MarketplaceDisplayType.DEFAULT, + ); const [ filterQuery, setFilterQuery ] = React.useState(defaultFilterQuery); const [ favoriteApps, setFavoriteApps ] = React.useState>([]); const [ isFavoriteAppsLoaded, setIsFavoriteAppsLoaded ] = React.useState(false); const [ isAppInfoModalOpen, setIsAppInfoModalOpen ] = React.useState(false); const [ isDisclaimerModalOpen, setIsDisclaimerModalOpen ] = React.useState(false); + const [ contractListModalType, setContractListModalType ] = React.useState(null); + const [ hasPreviousStep, setHasPreviousStep ] = React.useState(false); - const handleFavoriteClick = React.useCallback((id: string, isFavorite: boolean) => { - mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Favorite app', Info: id }); + const handleFavoriteClick = React.useCallback((id: string, isFavorite: boolean, source: 'Discovery view' | 'Security view' | 'App modal') => { + mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Favorite app', Info: id, Source: source }); const favoriteApps = getFavoriteApps(); @@ -60,11 +69,21 @@ export default function useMarketplace() { setIsDisclaimerModalOpen(true); }, []); + const showContractList = React.useCallback((id: string, type: ContractListTypes, hasPreviousStep?: boolean) => { + setSelectedAppId(id); + setContractListModalType(type); + if (hasPreviousStep) { + setHasPreviousStep(true); + } + }, []); + const debouncedFilterQuery = useDebounce(filterQuery, 500); const clearSelectedAppId = React.useCallback(() => { setSelectedAppId(null); setIsAppInfoModalOpen(false); setIsDisclaimerModalOpen(false); + setContractListModalType(null); + setHasPreviousStep(false); }, []); const handleCategoryChange = React.useCallback((newCategory: string) => { @@ -72,6 +91,10 @@ export default function useMarketplace() { setSelectedCategoryId(newCategory); }, []); + const handleDisplayTypeChange = React.useCallback((newDisplayType: MarketplaceDisplayType) => { + setSelectedDisplayType(newDisplayType); + }, []); + const { isPlaceholderData, isError, error, data, displayedApps, } = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps, isFavoriteAppsLoaded); @@ -97,6 +120,7 @@ export default function useMarketplace() { const query = _pickBy({ category: selectedCategoryId === MarketplaceCategory.ALL ? undefined : selectedCategoryId, filter: debouncedFilterQuery, + tab: selectedDisplayType === MarketplaceDisplayType.DEFAULT ? undefined : selectedDisplayType, }, Boolean); if (debouncedFilterQuery.length > 0) { @@ -111,7 +135,7 @@ export default function useMarketplace() { // omit router in the deps because router.push() somehow modifies it // and we get infinite re-renders then // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ debouncedFilterQuery, selectedCategoryId ]); + }, [ debouncedFilterQuery, selectedCategoryId, selectedDisplayType ]); return React.useMemo(() => ({ selectedCategoryId, @@ -133,6 +157,11 @@ export default function useMarketplace() { showDisclaimer, appsTotal: data?.length || 0, isCategoriesPlaceholderData, + showContractList, + contractListModalType, + selectedDisplayType, + onDisplayTypeChange: handleDisplayTypeChange, + hasPreviousStep, }), [ selectedCategoryId, categories, @@ -152,5 +181,10 @@ export default function useMarketplace() { showDisclaimer, data?.length, isCategoriesPlaceholderData, + showContractList, + contractListModalType, + selectedDisplayType, + handleDisplayTypeChange, + hasPreviousStep, ]); } diff --git a/ui/marketplace/useMarketplaceApps.tsx b/ui/marketplace/useMarketplaceApps.tsx index 93b2d27da5..8aca496b9c 100644 --- a/ui/marketplace/useMarketplaceApps.tsx +++ b/ui/marketplace/useMarketplaceApps.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import React from 'react'; -import type { MarketplaceAppOverview } from 'types/client/marketplace'; +import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'; import { MarketplaceCategory } from 'types/client/marketplace'; import config from 'configs/app'; @@ -10,19 +10,21 @@ import useApiFetch from 'lib/api/useApiFetch'; import useFetch from 'lib/hooks/useFetch'; import { MARKETPLACE_APP } from 'stubs/marketplace'; +import useSecurityReports from './useSecurityReports'; + const feature = config.features.marketplace; -function isAppNameMatches(q: string, app: MarketplaceAppOverview) { +function isAppNameMatches(q: string, app: MarketplaceAppWithSecurityReport) { return app.title.toLowerCase().includes(q.toLowerCase()); } -function isAppCategoryMatches(category: string, app: MarketplaceAppOverview, favoriteApps: Array) { +function isAppCategoryMatches(category: string, app: MarketplaceAppWithSecurityReport, favoriteApps: Array) { return category === MarketplaceCategory.ALL || (category === MarketplaceCategory.FAVORITES && favoriteApps.includes(app.id)) || app.categories.includes(category); } -function sortApps(apps: Array, favoriteApps: Array) { +function sortApps(apps: Array, favoriteApps: Array) { return apps.sort((a, b) => { const priorityA = a.priority || 0; const priorityB = b.priority || 0; @@ -56,6 +58,8 @@ export default function useMarketplaceApps( const fetch = useFetch(); const apiFetch = useApiFetch(); + const { data: securityReports, isPlaceholderData: isSecurityReportsPlaceholderData } = useSecurityReports(); + // Update favorite apps only when selectedCategoryId changes to avoid sortApps to be called on each favorite app click const [ snapshotFavoriteApps, setSnapshotFavoriteApps ] = React.useState | undefined>(); @@ -65,38 +69,43 @@ export default function useMarketplaceApps( } }, [ selectedCategoryId, isFavoriteAppsLoaded ]); // eslint-disable-line react-hooks/exhaustive-deps - const { isPlaceholderData, isError, error, data } = useQuery, Array>({ + const { isPlaceholderData, isError, error, data } = useQuery, Array>({ queryKey: [ 'marketplace-dapps', snapshotFavoriteApps, favoriteApps ], queryFn: async() => { if (!feature.isEnabled) { return []; } else if ('configUrl' in feature) { - return fetch, unknown>(feature.configUrl, undefined, { resource: 'marketplace-dapps' }); + return fetch, unknown>(feature.configUrl, undefined, { resource: 'marketplace-dapps' }); } else { return apiFetch('marketplace_dapps', { pathParams: { chainId: config.chain.id } }); } }, - select: (data) => sortApps(data as Array, snapshotFavoriteApps || []), + select: (data) => sortApps(data as Array, snapshotFavoriteApps || []), placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined, staleTime: Infinity, enabled: feature.isEnabled && (!favoriteApps || Boolean(snapshotFavoriteApps)), }); + const appsWithSecurityReports = React.useMemo(() => + data?.map((app) => ({ ...app, securityReport: securityReports?.[app.id] })), + [ data, securityReports ]); + const displayedApps = React.useMemo(() => { - return data?.filter(app => isAppNameMatches(filter, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps || [])) || []; - }, [ selectedCategoryId, data, filter, favoriteApps ]); + return appsWithSecurityReports?.filter(app => isAppNameMatches(filter, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps || [])) || []; + }, [ selectedCategoryId, appsWithSecurityReports, filter, favoriteApps ]); return React.useMemo(() => ({ data, displayedApps, error, isError, - isPlaceholderData, + isPlaceholderData: isPlaceholderData || isSecurityReportsPlaceholderData, }), [ data, displayedApps, error, isError, isPlaceholderData, + isSecurityReportsPlaceholderData, ]); } diff --git a/ui/marketplace/useMarketplaceWallet.tsx b/ui/marketplace/useMarketplaceWallet.tsx index e6b231bdeb..24e2f6858c 100644 --- a/ui/marketplace/useMarketplaceWallet.tsx +++ b/ui/marketplace/useMarketplaceWallet.tsx @@ -1,7 +1,7 @@ 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 { useAccount, useSendTransaction, useSwitchChain, useSignMessage, useSignTypedData } from 'wagmi'; import config from 'configs/app'; import * as mixpanel from 'lib/mixpanel/index'; @@ -9,7 +9,7 @@ import * as mixpanel from 'lib/mixpanel/index'; type SendTransactionArgs = { chainId?: number; mode?: 'prepared'; - to: string; + to: `0x${ string }`; }; export type SignTypedDataArgs< @@ -22,12 +22,11 @@ export type SignTypedDataArgs< > = SignTypedDataParameters; export default function useMarketplaceWallet(appId: string) { - const { address } = useAccount(); - const { chain } = useNetwork(); + const { address, chainId } = useAccount(); const { sendTransactionAsync } = useSendTransaction(); const { signMessageAsync } = useSignMessage(); const { signTypedDataAsync } = useSignTypedData(); - const { switchNetworkAsync } = useSwitchNetwork({ chainId: Number(config.chain.id) }); + const { switchChainAsync } = useSwitchChain(); const logEvent = useCallback((event: mixpanel.EventPayload['Action']) => { mixpanel.logEvent( @@ -37,16 +36,16 @@ export default function useMarketplaceWallet(appId: string) { }, [ address, appId ]); const switchNetwork = useCallback(async() => { - if (Number(config.chain.id) !== chain?.id) { - await switchNetworkAsync?.(); + if (Number(config.chain.id) !== chainId) { + await switchChainAsync?.({ chainId: Number(config.chain.id) }); } - }, [ chain, switchNetworkAsync ]); + }, [ chainId, switchChainAsync ]); const sendTransaction = useCallback(async(transaction: SendTransactionArgs) => { await switchNetwork(); const tx = await sendTransactionAsync(transaction); logEvent('Send Transaction'); - return tx.hash; + return tx; }, [ sendTransactionAsync, switchNetwork, logEvent ]); const signMessage = useCallback(async(message: string) => { diff --git a/ui/marketplace/useSecurityReports.tsx b/ui/marketplace/useSecurityReports.tsx new file mode 100644 index 0000000000..c15aef2d8a --- /dev/null +++ b/ui/marketplace/useSecurityReports.tsx @@ -0,0 +1,35 @@ +import { useQuery } from '@tanstack/react-query'; + +import type { MarketplaceAppSecurityReport, MarketplaceAppSecurityReportRaw } from 'types/client/marketplace'; + +import config from 'configs/app'; +import type { ResourceError } from 'lib/api/resources'; +import useApiFetch from 'lib/hooks/useFetch'; + +const feature = config.features.marketplace; +const securityReportsUrl = (feature.isEnabled && feature.securityReportsUrl) || ''; + +export default function useSecurityReports() { + const apiFetch = useApiFetch(); + + return useQuery, Record>({ + queryKey: [ 'marketplace-security-reports' ], + queryFn: async() => apiFetch(securityReportsUrl, undefined, { resource: 'marketplace-security-reports' }), + select: (data) => { + const securityReports: Record = {}; + (data as Array).forEach((item) => { + const report = item.chainsData[config.chain.id || '']; + if (report) { + const issues: Record = report.overallInfo.issueSeverityDistribution; + report.overallInfo.totalIssues = Object.values(issues).reduce((acc, val) => acc + val, 0); + report.overallInfo.securityScore = Number(report.overallInfo.securityScore.toFixed(2)); + } + securityReports[item.appName] = report; + }); + return securityReports; + }, + placeholderData: securityReportsUrl ? {} : undefined, + staleTime: Infinity, + enabled: Boolean(securityReportsUrl), + }); +} diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index 96a8a581b3..9f3d0b4da6 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -12,6 +12,7 @@ import useIsSafeAddress from 'lib/hooks/useIsSafeAddress'; import getQueryParamString from 'lib/router/getQueryParamString'; import { ADDRESS_TABS_COUNTERS } from 'stubs/address'; import { USER_OPS_ACCOUNT } from 'stubs/userOps'; +import AddressAccountHistory from 'ui/address/AddressAccountHistory'; import AddressBlocksValidated from 'ui/address/AddressBlocksValidated'; import AddressCoinBalance from 'ui/address/AddressCoinBalance'; import AddressContract from 'ui/address/AddressContract'; @@ -42,6 +43,8 @@ import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ]; +const txInterpretation = config.features.txInterpretation; + const AddressPageContent = () => { const router = useRouter(); const appProps = useAppContext(); @@ -80,6 +83,13 @@ const AddressPageContent = () => { count: addressTabsCountersQuery.data?.transactions_count, component: , }, + txInterpretation.isEnabled && txInterpretation.provider === 'noves' ? + { + id: 'account_history', + title: 'Account history', + component: , + } : + undefined, config.features.userOps.isEnabled && Boolean(userOpsAccountQuery.data?.total_ops) ? { id: 'user_ops', diff --git a/ui/pages/Blocks.pw.tsx b/ui/pages/Blocks.pw.tsx index 5b3776045c..95e0bc452a 100644 --- a/ui/pages/Blocks.pw.tsx +++ b/ui/pages/Blocks.pw.tsx @@ -1,19 +1,15 @@ -import { test as base, expect, devices } from '@playwright/experimental-ct-react'; +import type { BrowserContext } from '@playwright/test'; import React from 'react'; -import * as textAdMock from 'mocks/ad/textAd'; import * as blockMock from 'mocks/blocks/block'; import * as statsMock from 'mocks/stats/index'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import * as socketServer from 'playwright/fixtures/socketServer'; -import TestApp from 'playwright/TestApp'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; +import { test, expect, devices } from 'playwright/lib'; import * as configs from 'playwright/utils/configs'; import Blocks from './Blocks'; -const BLOCKS_API_URL = buildApiUrl('blocks') + '?type=block'; -const STATS_API_URL = buildApiUrl('stats'); const hooksConfig = { router: { query: { tab: 'blocks' }, @@ -21,70 +17,28 @@ const hooksConfig = { }, }; -const test = base.extend({ - createSocket: socketServer.createSocket, -}); - // FIXME // test cases which use socket cannot run in parallel since the socket server always run on the same port test.describe.configure({ mode: 'serial' }); -test.beforeEach(async({ page }) => { - await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ - status: 200, - body: JSON.stringify(textAdMock.duck), - })); - await page.route(textAdMock.duck.ad.thumbnail, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_s.jpg', - }); - }); -}); +test('base view +@dark-mode', async({ render, mockApiResponse }) => { + await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } }); + await mockApiResponse('stats', statsMock.base); -test('base view +@dark-mode', async({ mount, page }) => { - await page.route(BLOCKS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(blockMock.baseListResponse), - })); - await page.route(STATS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(statsMock.base), - })); - - const component = await mount( - - - , - { hooksConfig }, - ); - await page.waitForResponse(BLOCKS_API_URL); + const component = await render(, { hooksConfig }); await expect(component).toHaveScreenshot(); }); -const hiddenFieldsTest = test.extend({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields) as any, +const hiddenFieldsTest = test.extend<{ context: BrowserContext }>({ + context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields), }); -hiddenFieldsTest('hidden fields', async({ mount, page }) => { - await page.route(BLOCKS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(blockMock.baseListResponse), - })); - await page.route(STATS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(statsMock.base), - })); - - const component = await mount( - - - , - { hooksConfig }, - ); - await page.waitForResponse(BLOCKS_API_URL); +hiddenFieldsTest('hidden fields', async({ render, mockApiResponse }) => { + await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } }); + await mockApiResponse('stats', statsMock.base); + + const component = await render(, { hooksConfig }); await expect(component).toHaveScreenshot(); }); @@ -92,66 +46,33 @@ hiddenFieldsTest('hidden fields', async({ mount, page }) => { test.describe('mobile', () => { test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - test(' base view', async({ mount, page }) => { - await page.route(BLOCKS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(blockMock.baseListResponse), - })); - await page.route(STATS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(statsMock.base), - })); - - const component = await mount( - - - , - { hooksConfig }, - ); - await page.waitForResponse(BLOCKS_API_URL); + test(' base view', async({ render, mockApiResponse }) => { + await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } }); + await mockApiResponse('stats', statsMock.base); + + const component = await render(, { hooksConfig }); await expect(component).toHaveScreenshot(); }); - const hiddenFieldsTest = test.extend({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields) as any, + const hiddenFieldsTest = test.extend<{ context: BrowserContext }>({ + context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields), }); - hiddenFieldsTest('hidden fields', async({ mount, page }) => { - await page.route(BLOCKS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(blockMock.baseListResponse), - })); - await page.route(STATS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(statsMock.base), - })); - - const component = await mount( - - - , - { hooksConfig }, - ); - await page.waitForResponse(BLOCKS_API_URL); + hiddenFieldsTest('hidden fields', async({ render, mockApiResponse }) => { + await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } }); + await mockApiResponse('stats', statsMock.base); + + const component = await render(, { hooksConfig }); await expect(component).toHaveScreenshot(); }); }); -test('new item from socket', async({ mount, page, createSocket }) => { - await page.route(BLOCKS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(blockMock.baseListResponse), - })); +test('new item from socket', async({ render, mockApiResponse, createSocket }) => { + await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } }); - const component = await mount( - - - , - { hooksConfig }, - ); + const component = await render(, { hooksConfig }, { withSocket: true }); const socket = await createSocket(); const channel = await socketServer.joinChannel(socket, 'blocks:new_block'); @@ -167,18 +88,10 @@ test('new item from socket', async({ mount, page, createSocket }) => { await expect(component).toHaveScreenshot(); }); -test('socket error', async({ mount, page, createSocket }) => { - await page.route(BLOCKS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(blockMock.baseListResponse), - })); - - const component = await mount( - - - , - { hooksConfig }, - ); +test('socket error', async({ render, mockApiResponse, createSocket }) => { + await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } }); + + const component = await render(, { hooksConfig }, { withSocket: true }); const socket = await createSocket(); await socketServer.joinChannel(socket, 'blocks:new_block'); diff --git a/ui/pages/Marketplace.pw.tsx b/ui/pages/Marketplace.pw.tsx index b7228e5977..06a2e2cb85 100644 --- a/ui/pages/Marketplace.pw.tsx +++ b/ui/pages/Marketplace.pw.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { buildExternalAssetFilePath } from 'configs/app/utils'; import { apps as appsMock } from 'mocks/apps/apps'; +import { securityReports as securityReportsMock } from 'mocks/apps/securityReports'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import TestApp from 'playwright/TestApp'; import * as app from 'playwright/utils/app'; @@ -10,6 +11,8 @@ import * as app from 'playwright/utils/app'; import Marketplace from './Marketplace'; const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || ''; +const MARKETPLACE_SECURITY_REPORTS_URL = + app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', 'https://marketplace-security-reports.json') || ''; const test = base.extend({ context: contextWithEnvs([ @@ -41,3 +44,43 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => { await expect(component).toHaveScreenshot(); }); + +const testWithScoreFeature = test.extend({ + context: contextWithEnvs([ + { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL }, + { name: 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', value: MARKETPLACE_SECURITY_REPORTS_URL }, + { name: 'pw_feature:security_score_exp', value: 'true' }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ]) as any, +}); + +testWithScoreFeature('with scores +@mobile +@dark-mode', async({ mount, page }) => { + await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(appsMock), + })); + + await page.route(MARKETPLACE_SECURITY_REPORTS_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(securityReportsMock), + })); + + await Promise.all(appsMock.map(app => + page.route(app.logo, (route) => + route.fulfill({ + status: 200, + path: './playwright/mocks/image_s.jpg', + }), + ), + )); + + const component = await mount( + + + , + ); + + await component.getByText('Apps scores').click(); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/pages/Marketplace.tsx b/ui/pages/Marketplace.tsx index 1962559368..7a57b14c97 100644 --- a/ui/pages/Marketplace.tsx +++ b/ui/pages/Marketplace.tsx @@ -1,20 +1,25 @@ -import { Box, Menu, MenuButton, MenuItem, MenuList, Flex, IconButton } from '@chakra-ui/react'; +import { Box, Menu, MenuButton, MenuItem, MenuList, Flex, IconButton, Skeleton } from '@chakra-ui/react'; import React from 'react'; +import type { MouseEvent } from 'react'; -import { MarketplaceCategory } from 'types/client/marketplace'; +import { MarketplaceCategory, MarketplaceDisplayType } from 'types/client/marketplace'; import type { TabItem } from 'ui/shared/Tabs/types'; import config from 'configs/app'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; +import useFeatureValue from 'lib/growthbook/useFeatureValue'; import useIsMobile from 'lib/hooks/useIsMobile'; +import ContractListModal from 'ui/marketplace/ContractListModal'; import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal'; import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal'; import MarketplaceList from 'ui/marketplace/MarketplaceList'; +import MarketplaceListWithScores from 'ui/marketplace/MarketplaceListWithScores'; import FilterInput from 'ui/shared/filters/FilterInput'; import IconSvg from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; import PageTitle from 'ui/shared/Page/PageTitle'; +import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll'; @@ -60,8 +65,15 @@ const Marketplace = () => { showDisclaimer, appsTotal, isCategoriesPlaceholderData, + showContractList, + contractListModalType, + selectedDisplayType, + onDisplayTypeChange, + hasPreviousStep, } = useMarketplace(); + const isMobile = useIsMobile(); + const { value: isExperiment } = useFeatureValue('security_score_exp', false); const categoryTabs = React.useMemo(() => { const tabs: Array = categories.map(category => ({ @@ -80,7 +92,7 @@ const Marketplace = () => { tabs.unshift({ id: MarketplaceCategory.FAVORITES, - title: () => , + title: () => , count: null, component: null, }); @@ -93,18 +105,33 @@ const Marketplace = () => { return index === -1 ? 0 : index; }, [ categoryTabs, selectedCategoryId ]); + const selectedApp = displayedApps.find(app => app.id === selectedAppId); + const handleCategoryChange = React.useCallback((index: number) => { onCategoryChange(categoryTabs[index].id); }, [ categoryTabs, onCategoryChange ]); + const handleAppClick = React.useCallback((event: MouseEvent, id: string) => { + const isShown = window.localStorage.getItem('marketplace-disclaimer-shown'); + if (!isShown) { + event.preventDefault(); + showDisclaimer(id); + } + }, [ showDisclaimer ]); + + const handleGoBackInContractListModal = React.useCallback(() => { + clearSelectedAppId(); + if (selectedApp) { + showAppInfo(selectedApp.id); + } + }, [ clearSelectedAppId, showAppInfo, selectedApp ]); + throwOnResourceLoadError(isError && error ? { isError, error } : { isError: false, error: null }); if (!feature.isEnabled) { return null; } - const selectedApp = displayedApps.find(app => app.id === selectedAppId); - return ( <> { tabs={ categoryTabs } onTabChange={ handleCategoryChange } defaultTabIndex={ selectedCategoryIndex } - marginBottom={{ base: 0, lg: -2 }} + marginBottom={ -2 } /> ) } - + + { (feature.securityReportsUrl && isExperiment) && ( + + + onChange={ onDisplayTypeChange } + defaultValue={ selectedDisplayType } + name="type" + options={ [ + { + title: 'Discovery', + value: MarketplaceDisplayType.DEFAULT, + icon: 'apps_xs', + onlyIcon: false, + }, + { + title: 'Apps scores', + value: MarketplaceDisplayType.SCORES, + icon: 'apps_list', + onlyIcon: false, + contentAfter: ( + + ), + }, + ] } + autoWidth + /> + + ) } + + - + { (selectedDisplayType === MarketplaceDisplayType.SCORES && feature.securityReportsUrl && isExperiment) ? ( + + ) : ( + + ) } { (selectedApp && isAppInfoModalOpen) && ( { isFavorite={ favoriteApps.includes(selectedApp.id) } onFavoriteClick={ onFavoriteClick } data={ selectedApp } + showContractList={ showContractList } /> ) } @@ -188,6 +263,15 @@ const Marketplace = () => { appId={ selectedApp.id } /> ) } + + { (selectedApp && contractListModalType) && ( + + ) } ); }; diff --git a/ui/pages/MarketplaceApp.tsx b/ui/pages/MarketplaceApp.tsx index cf39542585..c04ba4fecf 100644 --- a/ui/pages/MarketplaceApp.tsx +++ b/ui/pages/MarketplaceApp.tsx @@ -20,6 +20,7 @@ import ContentLoader from 'ui/shared/ContentLoader'; import MarketplaceAppTopBar from '../marketplace/MarketplaceAppTopBar'; import useAutoConnectWallet from '../marketplace/useAutoConnectWallet'; import useMarketplaceWallet from '../marketplace/useMarketplaceWallet'; +import useSecurityReports from '../marketplace/useSecurityReports'; const feature = config.features.marketplace; @@ -104,6 +105,8 @@ const MarketplaceApp = () => { const { address, sendTransaction, signMessage, signTypedData } = useMarketplaceWallet(id); useAutoConnectWallet(); + const { data: securityReports, isLoading: isSecurityReportsLoading } = useSecurityReports(); + const query = useQuery, MarketplaceAppOverview>({ queryKey: [ 'marketplace-dapps', id ], queryFn: async() => { @@ -140,7 +143,12 @@ const MarketplaceApp = () => { return ( <> - + { Email diff --git a/ui/pages/OptimisticL2Withdrawals.tsx b/ui/pages/OptimisticL2Withdrawals.tsx index 43ec837ad2..6e941ff3de 100644 --- a/ui/pages/OptimisticL2Withdrawals.tsx +++ b/ui/pages/OptimisticL2Withdrawals.tsx @@ -39,7 +39,7 @@ const OptimisticL2Withdrawals = () => { <> { data.items.map(((item, index) => ( diff --git a/ui/pages/Token.tsx b/ui/pages/Token.tsx index aedc8ce28e..8ffcf9c455 100644 --- a/ui/pages/Token.tsx +++ b/ui/pages/Token.tsx @@ -17,8 +17,10 @@ import * as metadata from 'lib/metadata'; import getQueryParamString from 'lib/router/getQueryParamString'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; +import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import * as addressStubs from 'stubs/address'; import * as tokenStubs from 'stubs/token'; +import { getTokenHoldersStub } from 'stubs/token'; import { generateListStub } from 'stubs/utils'; import AddressContract from 'ui/address/AddressContract'; import AddressQrCode from 'ui/address/details/AddressQrCode'; @@ -118,7 +120,7 @@ const TokenPageContent = () => { }, [ tokenQuery.data, tokenQuery.isPlaceholderData ]); const hasData = (tokenQuery.data && !tokenQuery.isPlaceholderData) && (contractQuery.data && !contractQuery.isPlaceholderData); - const hasInventoryTab = tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721'; + const hasInventoryTab = tokenQuery.data?.type && NFT_TOKEN_TYPE_IDS.includes(tokenQuery.data.type); const transfersQuery = useQueryWithPages({ resourceName: 'token_transfers', @@ -161,8 +163,7 @@ const TokenPageContent = () => { scrollRef, options: { enabled: Boolean(hashString && tab === 'holders' && hasData), - placeholderData: generateListStub<'token_holders'>( - tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_HOLDER_ERC_1155 : tokenStubs.TOKEN_HOLDER_ERC_20, 50, { next_page_params: null }), + placeholderData: getTokenHoldersStub(tokenQuery.data?.type, null), }, }); @@ -174,7 +175,7 @@ const TokenPageContent = () => { const contractTabs = useContractTabs(contractQuery.data); const tabs: Array = [ - (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ? { + hasInventoryTab ? { id: 'inventory', title: 'Inventory', component: , @@ -212,7 +213,7 @@ const TokenPageContent = () => { } // default tab for nfts is token inventory - if (((tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') && !tab) || tab === 'inventory') { + if ((hasInventoryTab && !tab) || tab === 'inventory') { pagination = inventoryQuery.pagination; } diff --git a/ui/pages/TokenInstance.tsx b/ui/pages/TokenInstance.tsx index dba5eed48f..6471e822b9 100644 --- a/ui/pages/TokenInstance.tsx +++ b/ui/pages/TokenInstance.tsx @@ -11,9 +11,12 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import useIsMobile from 'lib/hooks/useIsMobile'; import * as metadata from 'lib/metadata'; import * as regexp from 'lib/regexp'; -import { TOKEN_INSTANCE, TOKEN_INFO_ERC_1155 } from 'stubs/token'; -import * as tokenStubs from 'stubs/token'; -import { generateListStub } from 'stubs/utils'; +import { + TOKEN_INSTANCE, + TOKEN_INFO_ERC_1155, + getTokenInstanceTransfersStub, + getTokenInstanceHoldersStub, +} from 'stubs/token'; import AddressQrCode from 'ui/address/details/AddressQrCode'; import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; import TextAd from 'ui/shared/ad/TextAd'; @@ -66,11 +69,7 @@ const TokenInstanceContent = () => { scrollRef, options: { enabled: Boolean(hash && id && (!tab || tab === 'token_transfers') && !tokenInstanceQuery.isPlaceholderData && tokenInstanceQuery.data), - placeholderData: generateListStub<'token_instance_transfers'>( - tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_TRANSFER_ERC_1155 : tokenStubs.TOKEN_TRANSFER_ERC_721, - 10, - { next_page_params: null }, - ), + placeholderData: getTokenInstanceTransfersStub(tokenQuery.data?.type, null), }, }); @@ -86,8 +85,7 @@ const TokenInstanceContent = () => { scrollRef, options: { enabled: Boolean(hash && tab === 'holders' && shouldFetchHolders), - placeholderData: generateListStub<'token_instance_holders'>( - tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_HOLDER_ERC_1155 : tokenStubs.TOKEN_HOLDER_ERC_20, 10, { next_page_params: null }), + placeholderData: getTokenInstanceHoldersStub(tokenQuery.data?.type, null), }, }); diff --git a/ui/pages/Transaction.tsx b/ui/pages/Transaction.tsx index 07ccc1d687..8e14c1d6cf 100644 --- a/ui/pages/Transaction.tsx +++ b/ui/pages/Transaction.tsx @@ -15,6 +15,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import useTabIndexFromQuery from 'ui/shared/Tabs/useTabIndexFromQuery'; +import TxAssetFlows from 'ui/tx/TxAssetFlows'; import TxBlobs from 'ui/tx/TxBlobs'; import TxDetails from 'ui/tx/TxDetails'; import TxDetailsDegraded from 'ui/tx/TxDetailsDegraded'; @@ -28,6 +29,8 @@ import TxTokenTransfer from 'ui/tx/TxTokenTransfer'; import TxUserOps from 'ui/tx/TxUserOps'; import useTxQuery from 'ui/tx/useTxQuery'; +const txInterpretation = config.features.txInterpretation; + const TransactionPageContent = () => { const router = useRouter(); const appProps = useAppContext(); @@ -36,7 +39,7 @@ const TransactionPageContent = () => { const txQuery = useTxQuery(); const { data, isPlaceholderData, isError, error, errorUpdateCount } = txQuery; - const showDegradedView = publicClient && (isError || isPlaceholderData) && errorUpdateCount > 0; + const showDegradedView = publicClient && ((isError && error.status !== 422) || isPlaceholderData) && errorUpdateCount > 0; const tabs: Array = (() => { const detailsComponent = showDegradedView ? @@ -49,6 +52,9 @@ const TransactionPageContent = () => { title: config.features.suave.isEnabled && data?.wrapped ? 'Confidential compute tx details' : 'Details', component: detailsComponent, }, + txInterpretation.isEnabled && txInterpretation.provider === 'noves' ? + { id: 'asset_flows', title: 'Asset Flows', component: } : + undefined, config.features.suave.isEnabled && data?.wrapped ? { id: 'wrapped', title: 'Regular tx details', component: } : undefined, diff --git a/ui/pages/Transactions.tsx b/ui/pages/Transactions.tsx index 631ac33874..4517b4d985 100644 --- a/ui/pages/Transactions.tsx +++ b/ui/pages/Transactions.tsx @@ -30,7 +30,7 @@ const Transactions = () => { const tab = getQueryParamString(router.query.tab); React.useEffect(() => { - if (tab === 'blob_txs' && config.UI.views.tx.hiddenViews?.blob_txs) { + if (tab === 'blob_txs' && !config.features.dataAvailability.isEnabled) { router.replace({ pathname: '/txs' }, undefined, { shallow: true }); } }, [ router, tab ]); @@ -66,7 +66,7 @@ const Transactions = () => { resourceName: 'txs_with_blobs', filters: { type: 'blob_transaction' }, options: { - enabled: !config.UI.views.tx.hiddenViews?.blob_txs && tab === 'blob_txs', + enabled: config.features.dataAvailability.isEnabled && tab === 'blob_txs', placeholderData: generateListStub<'txs_with_blobs'>(TX, 50, { next_page_params: { block_number: 10602877, index: 8, @@ -115,7 +115,7 @@ const Transactions = () => { /> ), }, - !config.UI.views.tx.hiddenViews?.blob_txs && { + config.features.dataAvailability.isEnabled && { id: 'blob_txs', title: 'Blob txns', component: ( diff --git a/ui/pages/Validators.pw.tsx b/ui/pages/Validators.pw.tsx index 3167d0bd7b..4ea86e6fe4 100644 --- a/ui/pages/Validators.pw.tsx +++ b/ui/pages/Validators.pw.tsx @@ -1,51 +1,22 @@ -import { test as base, expect } from '@playwright/experimental-ct-react'; +import type { BrowserContext } from '@playwright/test'; import React from 'react'; -import * as textAdMock from 'mocks/ad/textAd'; import * as validatorsMock from 'mocks/validators/index'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; -import TestApp from 'playwright/TestApp'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; +import { test as base, expect } from 'playwright/lib'; import * as configs from 'playwright/utils/configs'; import Validators from './Validators'; -const VALIDATORS_API_URL = buildApiUrl('validators', { chainType: 'stability' }); -const VALIDATORS_COUNTERS_API_URL = buildApiUrl('validators_counters', { chainType: 'stability' }); - -const test = base.extend({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - context: contextWithEnvs(configs.featureEnvs.validators) as any, -}); - -test.beforeEach(async({ page }) => { - await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ - status: 200, - body: JSON.stringify(textAdMock.duck), - })); - await page.route(textAdMock.duck.ad.thumbnail, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_s.jpg', - }); - }); +const test = base.extend<{ context: BrowserContext }>({ + context: contextWithEnvs(configs.featureEnvs.validators), }); -test('base view +@mobile', async({ mount, page }) => { - await page.route(VALIDATORS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(validatorsMock.validatorsResponse), - })); - await page.route(VALIDATORS_COUNTERS_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify(validatorsMock.validatorsCountersResponse), - })); +test('base view +@mobile', async({ render, mockApiResponse }) => { + await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType: 'stability' } }); + await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType: 'stability' } }); - const component = await mount( - - - , - ); + const component = await render(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/pages/VerifiedAddresses.pw.tsx b/ui/pages/VerifiedAddresses.pw.tsx index 766f8af1d2..4b64d5e23b 100644 --- a/ui/pages/VerifiedAddresses.pw.tsx +++ b/ui/pages/VerifiedAddresses.pw.tsx @@ -1,18 +1,12 @@ -import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as mocks from 'mocks/account/verifiedAddresses'; import * as profileMock from 'mocks/user/profile'; import authFixture from 'playwright/fixtures/auth'; -import TestApp from 'playwright/TestApp'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; +import { test as base, expect } from 'playwright/lib'; import VerifiedAddresses from './VerifiedAddresses'; -const VERIFIED_ADDRESS_URL = buildApiUrl('verified_addresses', { chainId: '1' }); -const TOKEN_INFO_APPLICATIONS_URL = buildApiUrl('token_info_applications', { chainId: '1', id: undefined }); -const USER_INFO_URL = buildApiUrl('user_info'); - const test = base.extend({ context: ({ context }, use) => { authFixture(context); @@ -20,90 +14,37 @@ const test = base.extend({ }, }); -test.beforeEach(async({ context }) => { - await context.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_s.jpg', - }); - }); +test.beforeEach(async({ mockAssetResponse }) => { + await mockAssetResponse(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, './playwright/mocks/image_s.jpg'); }); -test('base view +@mobile', async({ mount, page }) => { - await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT), - })); - - await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT), - })); - - await page.route(USER_INFO_URL, (route) => route.fulfill({ - body: JSON.stringify(profileMock.base), - })); - - const component = await mount( - - - , - ); +test('base view +@mobile', async({ render, mockApiResponse }) => { + await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } }); + await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT, { pathParams: { chainId: '1', id: undefined } }); + await mockApiResponse('user_info', profileMock.base); + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('user without email', async({ mount, page }) => { - await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT), - })); +test('user without email', async({ render, mockApiResponse }) => { + await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } }); + await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT, { pathParams: { chainId: '1', id: undefined } }); + await mockApiResponse('user_info', profileMock.withoutEmail); - await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT), - })); - - await page.route(USER_INFO_URL, (route) => route.fulfill({ - body: JSON.stringify(profileMock.withoutEmail), - })); - - const component = await mount( - - - , - ); + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('address verification flow', async({ mount, page }) => { - const CHECK_ADDRESS_URL = buildApiUrl('address_verification', { chainId: '1', type: ':prepare' }); - const VERIFY_ADDRESS_URL = buildApiUrl('address_verification', { chainId: '1', type: ':verify' }); - - await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT), - })); - - await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT), - })); +test('address verification flow', async({ render, mockApiResponse, page }) => { + await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } }); + await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT, { pathParams: { chainId: '1', id: undefined } }); + await mockApiResponse('address_verification', mocks.ADDRESS_CHECK_RESPONSE.SUCCESS as never, { pathParams: { chainId: '1', type: ':prepare' } }); + await mockApiResponse('address_verification', mocks.ADDRESS_VERIFY_RESPONSE.SUCCESS as never, { pathParams: { chainId: '1', type: ':verify' } }); + await mockApiResponse('user_info', profileMock.base); - await page.route(CHECK_ADDRESS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.ADDRESS_CHECK_RESPONSE.SUCCESS), - })); - - await page.route(VERIFY_ADDRESS_URL, (route) => { - return route.fulfill({ - body: JSON.stringify(mocks.ADDRESS_VERIFY_RESPONSE.SUCCESS), - }); - }); - - await page.route(USER_INFO_URL, (route) => route.fulfill({ - body: JSON.stringify(profileMock.base), - })); - - await mount( - - - , - ); + await render(); // open modal await page.getByRole('button', { name: /add address/i }).click(); @@ -114,6 +55,7 @@ test('address verification flow', async({ mount, page }) => { await page.getByRole('button', { name: /continue/i }).click(); // fill second step + await page.getByText('Sign manually').click(); const signatureInput = page.getByLabel(/signature hash/i); await signatureInput.fill(mocks.SIGNATURE); await page.getByRole('button', { name: /verify/i }).click(); @@ -124,37 +66,20 @@ test('address verification flow', async({ mount, page }) => { await expect(page).toHaveScreenshot(); }); -test('application update flow', async({ mount, page }) => { - const TOKEN_INFO_APPLICATION_URL = buildApiUrl('token_info_applications', { chainId: '1', id: mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM.id }); - const FORM_CONFIG_URL = buildApiUrl('token_info_applications_config', { chainId: '1' }); - - await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT), - })); +test('application update flow', async({ render, mockApiResponse, page }) => { + await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } }); + await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.FOR_UPDATE, { pathParams: { chainId: '1', id: undefined } }); + await mockApiResponse('user_info', profileMock.base); + await mockApiResponse('token_info_applications_config', mocks.TOKEN_INFO_FORM_CONFIG, { pathParams: { chainId: '1' } }); - await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.FOR_UPDATE), - })); - - await page.route(FORM_CONFIG_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.TOKEN_INFO_FORM_CONFIG), - })); - - await page.route(USER_INFO_URL, (route) => route.fulfill({ - body: JSON.stringify(profileMock.base), - })); - - // PUT request - await page.route(TOKEN_INFO_APPLICATION_URL, (route) => route.fulfill({ - body: JSON.stringify(mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM), - })); - - await mount( - - - , + await mockApiResponse( + 'token_info_applications', + mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM as never, // this mock is for PUT request + { pathParams: { chainId: '1', id: mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM.id } }, ); + await render(); + // open form await page.locator('tr').filter({ hasText: 'waiting for update' }).locator('button[aria-label="edit"]').click(); diff --git a/ui/pages/ZkSyncL2TxnBatch.pw.tsx b/ui/pages/ZkSyncL2TxnBatch.pw.tsx new file mode 100644 index 0000000000..055a678cf3 --- /dev/null +++ b/ui/pages/ZkSyncL2TxnBatch.pw.tsx @@ -0,0 +1,60 @@ +import { test as base, expect, devices } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import * as zkSyncTxnBatchMock from 'mocks/zkSync/zkSyncTxnBatch'; +import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; +import TestApp from 'playwright/TestApp'; +import buildApiUrl from 'playwright/utils/buildApiUrl'; +import * as configs from 'playwright/utils/configs'; + +import ZkSyncL2TxnBatch from './ZkSyncL2TxnBatch'; + +const test = base.extend({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context: contextWithEnvs(configs.featureEnvs.zkSyncRollup) as any, +}); + +const hooksConfig = { + router: { + query: { number: String(zkSyncTxnBatchMock.base.number) }, + }, +}; + +test.beforeEach(async({ page }) => { + await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ + status: 200, + body: '', + })); + + await page.route(BATCH_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(zkSyncTxnBatchMock.base), + })); +}); + +const BATCH_API_URL = buildApiUrl('zksync_l2_txn_batch', { number: String(zkSyncTxnBatchMock.base.number) }); + +test('base view', async({ mount }) => { + const component = await mount( + + + , + { hooksConfig }, + ); + + await expect(component).toHaveScreenshot(); +}); + +test.describe('mobile', () => { + test.use({ viewport: devices['iPhone 13 Pro'].viewport }); + test('base view', async({ mount }) => { + const component = await mount( + + + , + { hooksConfig }, + ); + + await expect(component).toHaveScreenshot(); + }); +}); diff --git a/ui/pages/ZkSyncL2TxnBatch.tsx b/ui/pages/ZkSyncL2TxnBatch.tsx new file mode 100644 index 0000000000..14608aa143 --- /dev/null +++ b/ui/pages/ZkSyncL2TxnBatch.tsx @@ -0,0 +1,102 @@ +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { RoutedTab } from 'ui/shared/Tabs/types'; + +import useApiQuery from 'lib/api/useApiQuery'; +import { useAppContext } from 'lib/contexts/app'; +import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError'; +import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; +import useIsMobile from 'lib/hooks/useIsMobile'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import { TX } from 'stubs/tx'; +import { generateListStub } from 'stubs/utils'; +import { ZKSYNC_L2_TXN_BATCH } from 'stubs/zkSyncL2'; +import TextAd from 'ui/shared/ad/TextAd'; +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 TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; +import ZkSyncL2TxnBatchDetails from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchDetails'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; + +const TAB_LIST_PROPS = { + marginBottom: 0, + py: 5, + marginTop: -5, +}; + +const ZkSyncL2TxnBatch = () => { + const router = useRouter(); + const appProps = useAppContext(); + const number = getQueryParamString(router.query.number); + const tab = getQueryParamString(router.query.tab); + const isMobile = useIsMobile(); + + const batchQuery = useApiQuery('zksync_l2_txn_batch', { + pathParams: { number }, + queryOptions: { + enabled: Boolean(number), + placeholderData: ZKSYNC_L2_TXN_BATCH, + }, + }); + + const batchTxsQuery = useQueryWithPages({ + resourceName: 'zksync_l2_txn_batch_txs', + pathParams: { number }, + options: { + enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), + placeholderData: generateListStub<'zksync_l2_txn_batch_txs'>(TX, 50, { next_page_params: { + batch_number: '8122', + block_number: 1338932, + index: 0, + items_count: 50, + } }), + }, + }); + + throwOnAbsentParamError(number); + throwOnResourceLoadError(batchQuery); + + const tabs: Array = React.useMemo(() => ([ + { id: 'index', title: 'Details', component: }, + { id: 'txs', title: 'Transactions', component: }, + ].filter(Boolean)), [ batchQuery, batchTxsQuery ]); + + const backLink = React.useMemo(() => { + const hasGoBackLink = appProps.referrer && appProps.referrer.endsWith('/batches'); + + if (!hasGoBackLink) { + return; + } + + return { + label: 'Back to tx batches list', + url: appProps.referrer, + }; + }, [ appProps.referrer ]); + + const hasPagination = !isMobile && batchTxsQuery.pagination.isVisible && tab === 'txs'; + + return ( + <> + + + { batchQuery.isPlaceholderData ? + : ( + : null } + stickyEnabled={ hasPagination } + /> + ) } + + ); +}; + +export default ZkSyncL2TxnBatch; diff --git a/ui/pages/ZkSyncL2TxnBatches.pw.tsx b/ui/pages/ZkSyncL2TxnBatches.pw.tsx new file mode 100644 index 0000000000..8b0bf139a0 --- /dev/null +++ b/ui/pages/ZkSyncL2TxnBatches.pw.tsx @@ -0,0 +1,44 @@ +import { test as base, expect } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import * as zkSyncTxnBatchesMock from 'mocks/zkSync/zkSyncTxnBatches'; +import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; +import TestApp from 'playwright/TestApp'; +import buildApiUrl from 'playwright/utils/buildApiUrl'; +import * as configs from 'playwright/utils/configs'; + +import ZkSyncL2TxnBatches from './ZkSyncL2TxnBatches'; + +const test = base.extend({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context: contextWithEnvs(configs.featureEnvs.zkSyncRollup) as any, +}); + +const BATCHES_API_URL = buildApiUrl('zksync_l2_txn_batches'); +const BATCHES_COUNTERS_API_URL = buildApiUrl('zksync_l2_txn_batches_count'); + +test('base view +@mobile', async({ mount, page }) => { + test.slow(); + await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ + status: 200, + body: '', + })); + + await page.route(BATCHES_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(zkSyncTxnBatchesMock.baseResponse), + })); + + await page.route(BATCHES_COUNTERS_API_URL, (route) => route.fulfill({ + status: 200, + body: '9927', + })); + + const component = await mount( + + + , + ); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/pages/ZkSyncL2TxnBatches.tsx b/ui/pages/ZkSyncL2TxnBatches.tsx new file mode 100644 index 0000000000..d01bbef7de --- /dev/null +++ b/ui/pages/ZkSyncL2TxnBatches.tsx @@ -0,0 +1,83 @@ +import { Hide, Show, Skeleton, Text } from '@chakra-ui/react'; +import React from 'react'; + +import useApiQuery from 'lib/api/useApiQuery'; +import { generateListStub } from 'stubs/utils'; +import { ZKSYNC_L2_TXN_BATCHES_ITEM } from 'stubs/zkSyncL2'; +import DataListDisplay from 'ui/shared/DataListDisplay'; +import PageTitle from 'ui/shared/Page/PageTitle'; +import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; +import ZkSyncTxnBatchesListItem from 'ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesListItem'; +import ZkSyncTxnBatchesTable from 'ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTable'; + +const ZkSyncL2TxnBatches = () => { + const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ + resourceName: 'zksync_l2_txn_batches', + options: { + placeholderData: generateListStub<'zksync_l2_txn_batches'>( + ZKSYNC_L2_TXN_BATCHES_ITEM, + 50, + { + next_page_params: { + items_count: 50, + number: 9045200, + }, + }, + ), + }, + }); + + const countersQuery = useApiQuery('zksync_l2_txn_batches_count', { + queryOptions: { + placeholderData: 5231746, + }, + }); + + const content = data?.items ? ( + <> + + { data.items.map(((item, index) => ( + + ))) } + + + + ) : null; + + const text = (() => { + if (countersQuery.isError || isError || !data?.items.length) { + return null; + } + + return ( + + Tx batch + #{ data.items[0].number } to + #{ data.items[data.items.length - 1].number } + (total of { countersQuery.data?.toLocaleString() } batches) + + ); + })(); + + const actionBar = ; + + return ( + <> + + + + ); +}; + +export default ZkSyncL2TxnBatches; diff --git a/ui/pages/__screenshots__/Blob.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Blob.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png index 87527b5a93..41128e8e02 100644 Binary files a/ui/pages/__screenshots__/Blob.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Blob.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Blob.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Blob.pw.tsx_default_base-view-mobile-dark-mode-1.png index 6ab988b309..53cf7028de 100644 Binary files a/ui/pages/__screenshots__/Blob.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Blob.pw.tsx_default_base-view-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Blob.pw.tsx_default_without-data-1.png b/ui/pages/__screenshots__/Blob.pw.tsx_default_without-data-1.png index bde292e320..f8a97c6db3 100644 Binary files a/ui/pages/__screenshots__/Blob.pw.tsx_default_without-data-1.png and b/ui/pages/__screenshots__/Blob.pw.tsx_default_without-data-1.png differ diff --git a/ui/pages/__screenshots__/Blob.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Blob.pw.tsx_mobile_base-view-mobile-dark-mode-1.png index 2fdc492595..7db4844088 100644 Binary files a/ui/pages/__screenshots__/Blob.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Blob.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ 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 2cf3a7ec75..e2ed79ae58 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 1851dc3744..d600afbd5f 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 cd1326ee49..7fb3bdf096 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__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png index 442d62ab16..124c2b86d6 100644 Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-mobile-dark-mode-1.png new file mode 100644 index 0000000000..21e51e79fd Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png index 9136fb2f13..ab79db5a10 100644 Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-mobile-dark-mode-1.png new file mode 100644 index 0000000000..85c7dea12e Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png index fc6b4513ec..82585aee4f 100644 Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_with-scores-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_with-scores-mobile-dark-mode-1.png new file mode 100644 index 0000000000..5d8c323fe8 Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_with-scores-mobile-dark-mode-1.png differ diff --git a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png index d1d65b77cd..f03dd57c1e 100644 Binary files a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/MarketplaceApp.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 febacb309f..5a03d585ed 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 d88025d48d..e9a1435305 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-blob-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-blob-hash-mobile-1.png index 1237338964..f8f4960916 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-blob-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-blob-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 650e6f77af..e706aff342 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 aa030d6a62..17cc006cc0 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 22a47915c5..4997dd1819 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 77929a19f8..de1314a9cb 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_search-by-user-op-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-user-op-hash-mobile-1.png index a23bc41866..9fea2b448f 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-user-op-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-user-op-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 f6f6b4c8b3..60bf4b9a6b 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-blob-hash-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-blob-hash-mobile-1.png index f9b181af04..4c31703d79 100644 Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-blob-hash-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-blob-hash-mobile-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png index 6288560904..5873ca0484 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png index 39df838aae..d27cd36fc0 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png differ diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png index 191f0b5a25..94ca737987 100644 Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png differ diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png b/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png index 7f6f5f467c..0dad636021 100644 Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png differ diff --git a/ui/pages/__screenshots__/ZkSyncL2TxnBatch.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/ZkSyncL2TxnBatch.pw.tsx_default_base-view-1.png new file mode 100644 index 0000000000..48f02b27d4 Binary files /dev/null and b/ui/pages/__screenshots__/ZkSyncL2TxnBatch.pw.tsx_default_base-view-1.png differ diff --git a/ui/pages/__screenshots__/ZkSyncL2TxnBatch.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/ZkSyncL2TxnBatch.pw.tsx_default_mobile-base-view-1.png new file mode 100644 index 0000000000..6bd9369d23 Binary files /dev/null and b/ui/pages/__screenshots__/ZkSyncL2TxnBatch.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png new file mode 100644 index 0000000000..c61872abb9 Binary files /dev/null and b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png new file mode 100644 index 0000000000..a43ee77aaf Binary files /dev/null and b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/searchResults/SearchResultsInput.tsx b/ui/searchResults/SearchResultsInput.tsx index a50b82b4e4..3559b1978d 100644 --- a/ui/searchResults/SearchResultsInput.tsx +++ b/ui/searchResults/SearchResultsInput.tsx @@ -5,6 +5,7 @@ import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; import { getRecentSearchKeywords } from 'lib/recentSearchKeywords'; +import SearchBarBackdrop from 'ui/snippets/searchBar/SearchBarBackdrop'; import SearchBarInput from 'ui/snippets/searchBar/SearchBarInput'; import SearchBarRecentKeywords from 'ui/snippets/searchBar/SearchBarRecentKeywords'; @@ -66,33 +67,39 @@ const SearchResultsInput = ({ searchTerm, handleSubmit, handleSearchTermChange } }; }, [ calculateMenuWidth ]); + const isSuggestOpen = isOpen && recentSearchKeywords.length > 0 && searchTerm.trim().length === 0; + return ( - 0 && searchTerm.trim().length === 0 } - autoFocus={ false } - onClose={ onClose } - placement="bottom-start" - offset={ isMobile ? [ 16, -12 ] : undefined } - isLazy - > - - - - - - - - - + <> + + + + + + + + + + + + ); }; diff --git a/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_tx-not-found-1.png b/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_tx-not-found-1.png index ba19d9d787..bcb5755e61 100644 Binary files a/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_tx-not-found-1.png and b/ui/shared/AppError/__screenshots__/AppError.pw.tsx_default_tx-not-found-1.png differ diff --git a/ui/shared/AppError/custom/AppErrorTxNotFound.tsx b/ui/shared/AppError/custom/AppErrorTxNotFound.tsx index 6686f43582..ee8ee0b00f 100644 --- a/ui/shared/AppError/custom/AppErrorTxNotFound.tsx +++ b/ui/shared/AppError/custom/AppErrorTxNotFound.tsx @@ -1,7 +1,9 @@ /* eslint-disable max-len */ -import { Box, OrderedList, ListItem, useColorModeValue, Flex, chakra } from '@chakra-ui/react'; +import { Box, OrderedList, ListItem, useColorModeValue, Flex, chakra, Button } from '@chakra-ui/react'; import React from 'react'; +import { route } from 'nextjs-routes'; + import IconSvg from 'ui/shared/IconSvg'; import AppErrorTitle from '../AppErrorTitle'; @@ -51,6 +53,15 @@ const AppErrorTxNotFound = () => { for additional information. + ); }; diff --git a/ui/shared/Noves/NovesFromTo.tsx b/ui/shared/Noves/NovesFromTo.tsx new file mode 100644 index 0000000000..db1ea47205 --- /dev/null +++ b/ui/shared/Noves/NovesFromTo.tsx @@ -0,0 +1,62 @@ +import { Box, Skeleton } from '@chakra-ui/react'; +import type { FC } from 'react'; +import React from 'react'; + +import type { NovesResponseData } from 'types/api/noves'; + +import type { NovesFlowViewItem } from 'ui/tx/assetFlows/utils/generateFlowViewData'; + +import Tag from '../chakra/Tag'; +import AddressEntity from '../entities/address/AddressEntity'; +import { getActionFromTo, getFromTo } from './utils'; + +interface Props { + isLoaded: boolean; + txData?: NovesResponseData; + currentAddress?: string; + item?: NovesFlowViewItem; +} + +const NovesFromTo: FC = ({ isLoaded, txData, currentAddress = '', item }) => { + const data = React.useMemo(() => { + if (txData) { + return getFromTo(txData, currentAddress); + } + if (item) { + return getActionFromTo(item); + } + + return { text: 'Sent to', address: '' }; + }, [ currentAddress, item, txData ]); + + const isSent = data.text.startsWith('Sent'); + + const address = { hash: data.address || '', name: data.name || '' }; + + return ( + + + + { data.text } + + + + + + ); +}; + +export default NovesFromTo; diff --git a/ui/shared/Noves/utils.test.ts b/ui/shared/Noves/utils.test.ts new file mode 100644 index 0000000000..52bdc97a9c --- /dev/null +++ b/ui/shared/Noves/utils.test.ts @@ -0,0 +1,49 @@ +import * as transactionMock from 'mocks/noves/transaction'; +import type { NovesFlowViewItem } from 'ui/tx/assetFlows/utils/generateFlowViewData'; + +import { getActionFromTo, getFromTo, getFromToValue } from './utils'; + +it('get data for FromTo component from transaction', async() => { + const result = getFromTo(transactionMock.transaction, transactionMock.transaction.accountAddress); + + expect(result).toEqual({ + text: 'Sent to', + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + }); +}); + +it('get what type of FromTo component will be', async() => { + const result = getFromToValue(transactionMock.transaction, transactionMock.transaction.accountAddress); + + expect(result).toEqual('sent'); +}); + +it('get data for FromTo component from flow item', async() => { + const item: NovesFlowViewItem = { + action: { + label: 'Sent', + amount: '3000', + flowDirection: 'toRight', + nft: undefined, + token: { + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + decimals: 18, + name: 'PQR-Test', + symbol: 'PQR', + }, + }, + rightActor: { + address: '0xdD15D2650387Fb6FEDE27ae7392C402a393F8A37', + name: null, + }, + accountAddress: '0xef6595a423c99f3f2821190a4d96fce4dcd89a80', + }; + + const result = getActionFromTo(item); + + expect(result).toEqual({ + text: 'Sent to', + address: '0xdD15D2650387Fb6FEDE27ae7392C402a393F8A37', + name: null, + }); +}); diff --git a/ui/shared/Noves/utils.ts b/ui/shared/Noves/utils.ts new file mode 100644 index 0000000000..9e0bf88677 --- /dev/null +++ b/ui/shared/Noves/utils.ts @@ -0,0 +1,89 @@ +import type { NovesResponseData, NovesSentReceived } from 'types/api/noves'; + +import type { NovesFlowViewItem } from 'ui/tx/assetFlows/utils/generateFlowViewData'; + +export interface FromToData { + text: string; + address: string; + name?: string | null; +} + +export const getFromTo = (txData: NovesResponseData, currentAddress: string): FromToData => { + const raw = txData.rawTransactionData; + const sent = txData.classificationData.sent; + let sentFound: Array = []; + if (sent && sent[0]) { + sentFound = sent + .filter((sent) => sent.from.address.toLocaleLowerCase() === currentAddress) + .filter((sent) => sent.to.address); + } + + const received = txData.classificationData.received; + let receivedFound: Array = []; + if (received && received[0]) { + receivedFound = received + .filter((received) => received.to.address?.toLocaleLowerCase() === currentAddress) + .filter((received) => received.from.address); + } + + if (sentFound[0] && receivedFound[0]) { + if (sentFound.length === receivedFound.length) { + if (raw.toAddress.toLocaleLowerCase() === currentAddress) { + return { text: 'Received from', address: raw.fromAddress }; + } + + if (raw.fromAddress.toLocaleLowerCase() === currentAddress) { + return { text: 'Sent to', address: raw.toAddress }; + } + } + if (sentFound.length > receivedFound.length) { + // already filtered if null + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return { text: 'Sent to', address: sentFound[0].to.address! } ; + } else { + return { text: 'Received from', address: receivedFound[0].from.address } ; + } + } + + if (sent && sentFound[0]) { + // already filtered if null + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return { text: 'Sent to', address: sentFound[0].to.address! } ; + } + + if (received && receivedFound[0]) { + return { text: 'Received from', address: receivedFound[0].from.address }; + } + + if (raw.toAddress && raw.toAddress.toLocaleLowerCase() === currentAddress) { + return { text: 'Received from', address: raw.fromAddress }; + } + + if (raw.fromAddress && raw.fromAddress.toLocaleLowerCase() === currentAddress) { + return { text: 'Sent to', address: raw.toAddress }; + } + + if (!raw.toAddress && raw.fromAddress) { + return { text: 'Received from', address: raw.fromAddress }; + } + + if (!raw.fromAddress && raw.toAddress) { + return { text: 'Sent to', address: raw.toAddress }; + } + + return { text: 'Sent to', address: currentAddress }; +}; + +export const getFromToValue = (txData: NovesResponseData, currentAddress: string) => { + const fromTo = getFromTo(txData, currentAddress); + + return fromTo.text.split(' ').shift()?.toLowerCase(); +}; + +export const getActionFromTo = (item: NovesFlowViewItem): FromToData => { + return { + text: item.action.flowDirection === 'toRight' ? 'Sent to' : 'Received from', + address: item.rightActor.address, + name: item.rightActor.name, + }; +}; diff --git a/ui/shared/TokenTransfer/TokenTransferListItem.tsx b/ui/shared/TokenTransfer/TokenTransferListItem.tsx index 4d78789860..4707f9d917 100644 --- a/ui/shared/TokenTransfer/TokenTransferListItem.tsx +++ b/ui/shared/TokenTransfer/TokenTransferListItem.tsx @@ -35,7 +35,7 @@ const TokenTransferListItem = ({ isLoading, }: Props) => { const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement); - const { usd, valueStr } = 'value' in total ? getCurrencyValue({ + const { usd, valueStr } = 'value' in total && total.value !== null ? getCurrencyValue({ value: total.value, exchangeRate: token.exchange_rate, accuracy: 8, diff --git a/ui/shared/TokenTransfer/TokenTransferTable.tsx b/ui/shared/TokenTransfer/TokenTransferTable.tsx index b707ccbdc0..bcb006faf9 100644 --- a/ui/shared/TokenTransfer/TokenTransferTable.tsx +++ b/ui/shared/TokenTransfer/TokenTransferTable.tsx @@ -38,7 +38,7 @@ const TokenTransferTable = ({ { showTxInfo && } - + { showTxInfo && } diff --git a/ui/shared/TokenTransfer/TokenTransferTableItem.tsx b/ui/shared/TokenTransfer/TokenTransferTableItem.tsx index b93a917250..2d4ad7d1d6 100644 --- a/ui/shared/TokenTransfer/TokenTransferTableItem.tsx +++ b/ui/shared/TokenTransfer/TokenTransferTableItem.tsx @@ -34,7 +34,7 @@ const TokenTransferTableItem = ({ isLoading, }: Props) => { const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement); - const { usd, valueStr } = 'value' in total ? getCurrencyValue({ + const { usd, valueStr } = 'value' in total && total.value !== null ? getCurrencyValue({ value: total.value, exchangeRate: token.exchange_rate, accuracy: 8, @@ -52,14 +52,14 @@ const TokenTransferTableItem = ({ ) } - { token.type === 'ERC-1155' && } + { (token.type === 'ERC-1155' || token.type === 'ERC-404') && } - { token.total_supply && } + { token.total_supply && token.type !== 'ERC-404' && } diff --git a/ui/token/TokenHolders/TokenHoldersTableItem.tsx b/ui/token/TokenHolders/TokenHoldersTableItem.tsx index 8bfc187e0a..8bbb815548 100644 --- a/ui/token/TokenHolders/TokenHoldersTableItem.tsx +++ b/ui/token/TokenHolders/TokenHoldersTableItem.tsx @@ -26,7 +26,7 @@ const TokenTransferTableItem = ({ holder, token, isLoading }: Props) => { fontWeight="700" /> - { token.type === 'ERC-1155' && 'token_id' in holder && ( + { (token.type === 'ERC-1155' || token.type === 'ERC-404') && 'token_id' in holder && ( - { token.total_supply && ( + { token.total_supply && token.type !== 'ERC-404' && ( - - { (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && - + + { (NFT_TOKEN_TYPE_IDS.includes(tokenType)) && + } - { (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && ( - ) } diff --git a/ui/token/TokenTransfer/TokenTransferTableItem.tsx b/ui/token/TokenTransfer/TokenTransferTableItem.tsx index 4b782466db..63b2ee005f 100644 --- a/ui/token/TokenTransfer/TokenTransferTableItem.tsx +++ b/ui/token/TokenTransfer/TokenTransferTableItem.tsx @@ -5,6 +5,7 @@ import type { TokenTransfer } from 'types/api/tokenTransfer'; import getCurrencyValue from 'lib/getCurrencyValue'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; +import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; import Tag from 'ui/shared/chakra/Tag'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; @@ -24,7 +25,7 @@ const TokenTransferTableItem = ({ isLoading, }: Props) => { const timeAgo = useTimeAgoIncrement(timestamp, true); - const { usd, valueStr } = 'value' in total ? getCurrencyValue({ + const { usd, valueStr } = 'value' in total && total.value !== null ? getCurrencyValue({ value: total.value, exchangeRate: token.exchange_rate, accuracy: 8, @@ -69,7 +70,7 @@ const TokenTransferTableItem = ({ tokenHash={ token.address } /> - { (token.type === 'ERC-721' || token.type === 'ERC-1155') && ( + { (NFT_TOKEN_TYPE_IDS.includes(token.type)) && ( + + + + ); +}; + +export default React.memo(TxAssetFlowsTableItem); diff --git a/ui/tx/assetFlows/components/NovesActionSnippet.tsx b/ui/tx/assetFlows/components/NovesActionSnippet.tsx new file mode 100644 index 0000000000..c1acc6b0db --- /dev/null +++ b/ui/tx/assetFlows/components/NovesActionSnippet.tsx @@ -0,0 +1,116 @@ +import { Box, Hide, Popover, PopoverArrow, PopoverContent, PopoverTrigger, Show, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; +import type { FC } from 'react'; +import React from 'react'; + +import { HEX_REGEXP } from 'lib/regexp'; +import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; + +import type { NovesFlowViewItem } from '../utils/generateFlowViewData'; +import NovesTokenTooltipContent from './NovesTokenTooltipContent'; + +interface Props { + item: NovesFlowViewItem; + isLoaded: boolean; +} + +const NovesActionSnippet: FC = ({ item, isLoaded }) => { + const popoverBg = useColorModeValue('gray.700', 'gray.300'); + + const token = React.useMemo(() => { + const action = item.action; + + const name = action.nft?.name || action.token?.name; + const symbol = action.nft?.symbol || action.token?.symbol; + + const token = { + name: name, + symbol: symbol?.toLowerCase() === name?.toLowerCase() ? undefined : symbol, + address: action.nft?.address || action.token?.address, + }; + + return token; + }, [ item.action ]); + + const validTokenAddress = token.address ? HEX_REGEXP.test(token.address) : false; + + return ( + + + + + { item.action.label } + + + { item.action.amount } + + + + + + + + + + + + { item.action.label } + + + { item.action.amount } + + + + + + + + + + + + + + ); +}; + +export default React.memo(NovesActionSnippet); diff --git a/ui/tx/assetFlows/components/NovesTokenTooltipContent.tsx b/ui/tx/assetFlows/components/NovesTokenTooltipContent.tsx new file mode 100644 index 0000000000..f2bebb068c --- /dev/null +++ b/ui/tx/assetFlows/components/NovesTokenTooltipContent.tsx @@ -0,0 +1,55 @@ +import { Box, Text, useColorModeValue } from '@chakra-ui/react'; +import type { FC } from 'react'; +import React from 'react'; + +import type { NovesNft, NovesToken } from 'types/api/noves'; + +import { HEX_REGEXP } from 'lib/regexp'; +import CopyToClipboard from 'ui/shared/CopyToClipboard'; + +interface Props { + amount?: string; + token: NovesToken | NovesNft | undefined; +} + +const NovesTokenTooltipContent: FC = ({ token, amount }) => { + const textColor = useColorModeValue('white', 'blackAlpha.900'); + + if (!token) { + return null; + } + + const showTokenName = token.symbol !== token.name; + const showTokenAddress = HEX_REGEXP.test(token.address); + + return ( + + + + { amount } + + + { token.symbol } + + + + { showTokenName && ( + + { token.name } + + ) } + + { showTokenAddress && ( + + + { token.address } + + + + ) } + + + ); +}; + +export default React.memo(NovesTokenTooltipContent); diff --git a/ui/tx/assetFlows/utils/createNovesSummaryObject.test.ts b/ui/tx/assetFlows/utils/createNovesSummaryObject.test.ts new file mode 100644 index 0000000000..35275d22bc --- /dev/null +++ b/ui/tx/assetFlows/utils/createNovesSummaryObject.test.ts @@ -0,0 +1,20 @@ +import * as transactionMock from 'mocks/noves/transaction'; + +import { createNovesSummaryObject } from './createNovesSummaryObject'; + +it('creates interpretation summary object', async() => { + const result = createNovesSummaryObject(transactionMock.transaction); + + expect(result).toEqual({ + summary_template: ' Called function \'stake\' on contract{0xef326CdAdA59D3A740A76bB5f4F88Fb2}', + summary_template_variables: { + '0xef326CdAdA59D3A740A76bB5f4F88Fb2': { + type: 'address', + value: { + hash: '0xef326CdAdA59D3A740A76bB5f4F88Fb2f1076164', + is_contract: true, + }, + }, + }, + }); +}); diff --git a/ui/tx/assetFlows/utils/createNovesSummaryObject.ts b/ui/tx/assetFlows/utils/createNovesSummaryObject.ts new file mode 100644 index 0000000000..81c2e956f0 --- /dev/null +++ b/ui/tx/assetFlows/utils/createNovesSummaryObject.ts @@ -0,0 +1,139 @@ +import type { NovesResponseData } from 'types/api/noves'; +import type { TxInterpretationSummary } from 'types/api/txInterpretation'; + +import { createAddressValues } from './getAddressValues'; +import type { NovesTokenInfo, TokensData } from './getTokensData'; +import { getTokensData } from './getTokensData'; + +export interface SummaryAddress { + hash: string; + name?: string | null; + is_contract?: boolean; +} + +export interface SummaryValues { + match: string; + value: NovesTokenInfo | SummaryAddress; + type: 'token' | 'address'; +} + +interface NovesSummary { + summary_template: string; + summary_template_variables: {[x: string]: unknown}; +} + +export const createNovesSummaryObject = (translateData: NovesResponseData) => { + + // Remove final dot and add space at the start to avoid matching issues + const description = translateData.classificationData.description; + const removedFinalDot = description.endsWith('.') ? description.slice(0, description.length - 1) : description; + let parsedDescription = ' ' + removedFinalDot + ' '; + const tokenData = getTokensData(translateData); + + const idsMatched = tokenData.idList.filter(id => parsedDescription.includes(`#${ id }`)); + const tokensMatchedByName = tokenData.nameList.filter(name => parsedDescription.toUpperCase().includes(` ${ name.toUpperCase() }`)); + let tokensMatchedBySymbol = tokenData.symbolList.filter(symbol => parsedDescription.toUpperCase().includes(` ${ symbol.toUpperCase() }`)); + + // Filter symbols if they're already matched by name + tokensMatchedBySymbol = tokensMatchedBySymbol.filter(symbol => !tokensMatchedByName.includes(tokenData.bySymbol[symbol]?.name || '')); + + const summaryValues: Array = []; + + if (idsMatched.length) { + parsedDescription = removeIds(tokensMatchedByName, tokensMatchedBySymbol, idsMatched, tokenData, parsedDescription); + } + + if (tokensMatchedByName.length) { + const values = createTokensSummaryValues(tokensMatchedByName, tokenData.byName); + summaryValues.push(...values); + } + + if (tokensMatchedBySymbol.length) { + const values = createTokensSummaryValues(tokensMatchedBySymbol, tokenData.bySymbol); + summaryValues.push(...values); + } + + const addressSummaryValues = createAddressValues(translateData, parsedDescription); + if (addressSummaryValues.length) { + summaryValues.push(...addressSummaryValues); + } + + return createSummaryTemplate(summaryValues, parsedDescription) as TxInterpretationSummary; +}; + +const removeIds = ( + tokensMatchedByName: Array, + tokensMatchedBySymbol: Array, + idsMatched: Array, + tokenData: TokensData, + parsedDescription: string, +) => { + // Remove ids from the description since we already have that info in the token object + let description = parsedDescription; + + tokensMatchedByName.forEach(name => { + const hasId = idsMatched.includes(tokenData.byName[name].id || ''); + if (hasId) { + description = description.replaceAll(`#${ tokenData.byName[name].id }`, ''); + } + }); + + tokensMatchedBySymbol.forEach(name => { + const hasId = idsMatched.includes(tokenData.bySymbol[name].id || ''); + if (hasId) { + description = description.replaceAll(`#${ tokenData.bySymbol[name].id }`, ''); + } + }); + + return description; +}; + +const createTokensSummaryValues = ( + matchedStrings: Array, + tokens: { + [x: string]: NovesTokenInfo; + }, +) => { + const summaryValues: Array = matchedStrings.map(match => ({ + match, + type: 'token', + value: tokens[match], + })); + + return summaryValues; +}; + +const createSummaryTemplate = (summaryValues: Array, parsedDescription: string) => { + let newDescription = parsedDescription; + + const result: NovesSummary = { + summary_template: newDescription, + summary_template_variables: {}, + }; + + if (!summaryValues[0]) { + return result; + } + + const createTemplate = (data: SummaryValues, index = 0) => { + newDescription = newDescription.replaceAll(new RegExp(` ${ data.match } `, 'gi'), `{${ data.match }}`); + + const variable = { + type: data.type, + value: data.value, + }; + + result.summary_template_variables[data.match] = variable; + + const nextValue = summaryValues[index + 1]; + if (nextValue) { + createTemplate(nextValue, index + 1); + } + }; + + createTemplate(summaryValues[0]); + + result.summary_template = newDescription; + + return result; +}; diff --git a/ui/tx/assetFlows/utils/generateFlowViewData.test.ts b/ui/tx/assetFlows/utils/generateFlowViewData.test.ts new file mode 100644 index 0000000000..0cbb2770dd --- /dev/null +++ b/ui/tx/assetFlows/utils/generateFlowViewData.test.ts @@ -0,0 +1,48 @@ +import * as transactionMock from 'mocks/noves/transaction'; + +import { generateFlowViewData } from './generateFlowViewData'; + +it('creates asset flows items', async() => { + const result = generateFlowViewData(transactionMock.transaction); + + expect(result).toEqual( + [ + { + action: { + label: 'Sent', + amount: '3000', + flowDirection: 'toRight', + token: { + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + decimals: 18, + name: 'PQR-Test', + symbol: 'PQR', + }, + }, + rightActor: { + address: '0xdD15D2650387Fb6FEDE27ae7392C402a393F8A37', + name: null, + }, + accountAddress: '0xef6595a423c99f3f2821190a4d96fce4dcd89a80', + }, + { + action: { + label: 'Paid Gas', + amount: '0.000395521502109448', + flowDirection: 'toRight', + token: { + address: 'ETH', + decimals: 18, + name: 'ETH', + symbol: 'ETH', + }, + }, + rightActor: { + address: '', + name: 'Validators', + }, + accountAddress: '0xef6595a423c99f3f2821190a4d96fce4dcd89a80', + }, + ], + ); +}); diff --git a/ui/tx/assetFlows/utils/generateFlowViewData.ts b/ui/tx/assetFlows/utils/generateFlowViewData.ts new file mode 100644 index 0000000000..9564c574d8 --- /dev/null +++ b/ui/tx/assetFlows/utils/generateFlowViewData.ts @@ -0,0 +1,76 @@ +import _ from 'lodash'; + +import type { NovesNft, NovesResponseData, NovesSentReceived, NovesToken } from 'types/api/noves'; + +export interface NovesAction { + label: string; + amount: string | undefined; + flowDirection: 'toLeft' | 'toRight'; + nft: NovesNft | undefined; + token: NovesToken | undefined; +} + +export interface NovesFlowViewItem { + action: NovesAction; + rightActor: { + address: string ; + name: string | null; + }; + accountAddress: string; +} + +export function generateFlowViewData(data: NovesResponseData): Array { + const perspectiveAddress = data.accountAddress.toLowerCase(); + + const sent = data.classificationData.sent || []; + const received = data.classificationData.received || []; + + const txItems = [ ...sent, ...received ]; + + const paidGasIndex = _.findIndex(txItems, (item) => item.action === 'paidGas'); + if (paidGasIndex >= 0) { + const element = txItems.splice(paidGasIndex, 1)[0]; + element.to.name = 'Validators'; + txItems.splice(txItems.length, 0, element); + } + + const flowViewData = txItems.map((item) => { + const action = { + label: item.actionFormatted || item.action, + amount: item.amount || undefined, + flowDirection: getFlowDirection(item, perspectiveAddress), + nft: item.nft || undefined, + token: item.token || undefined, + }; + + if (item.from.name && item.from.name.includes('(this wallet)')) { + item.from.name = item.from.name.split('(this wallet)')[0]; + } + + if (item.to.name && item.to.name.includes('(this wallet)')) { + item.to.name = item.to.name.split('(this wallet)')[0]; + } + + const rightActor = getRightActor(item, perspectiveAddress); + + return { action, rightActor, accountAddress: perspectiveAddress }; + }); + + return flowViewData; +} + +function getRightActor(item: NovesSentReceived, perspectiveAddress: string) { + if (!item.to.address || item.to.address.toLowerCase() !== perspectiveAddress) { + return { address: item.to.address || '', name: item.to.name }; + } + + return { address: item.from.address, name: item.from.name }; +} + +function getFlowDirection(item: NovesSentReceived, perspectiveAddress: string): 'toLeft' | 'toRight' { + if (item.from.address && item.from.address.toLowerCase() === perspectiveAddress) { + return 'toRight'; + } + + return 'toLeft'; +} diff --git a/ui/tx/assetFlows/utils/getAddressValues.test.ts b/ui/tx/assetFlows/utils/getAddressValues.test.ts new file mode 100644 index 0000000000..f911253cce --- /dev/null +++ b/ui/tx/assetFlows/utils/getAddressValues.test.ts @@ -0,0 +1,18 @@ +import * as transactionMock from 'mocks/noves/transaction'; + +import { createAddressValues } from './getAddressValues'; + +it('creates addresses summary values', async() => { + const result = createAddressValues(transactionMock.transaction, transactionMock.transaction.classificationData.description); + + expect(result).toEqual([ + { + match: '0xef326CdAdA59D3A740A76bB5f4F88Fb2', + type: 'address', + value: { + hash: '0xef326CdAdA59D3A740A76bB5f4F88Fb2f1076164', + is_contract: true, + }, + }, + ]); +}); diff --git a/ui/tx/assetFlows/utils/getAddressValues.ts b/ui/tx/assetFlows/utils/getAddressValues.ts new file mode 100644 index 0000000000..7e20b92843 --- /dev/null +++ b/ui/tx/assetFlows/utils/getAddressValues.ts @@ -0,0 +1,80 @@ +import type { NovesResponseData } from 'types/api/noves'; + +import type { SummaryAddress, SummaryValues } from './createNovesSummaryObject'; + +const ADDRESS_REGEXP = /(0x[\da-f]+\b)/gi; +const CONTRACT_REGEXP = /(contract 0x[\da-f]+\b)/gi; + +export const createAddressValues = (translateData: NovesResponseData, description: string) => { + const addressMatches = description.match(ADDRESS_REGEXP); + const contractMatches = description.match(CONTRACT_REGEXP); + + let descriptionAddresses: Array = addressMatches ? addressMatches : []; + let contractAddresses: Array = []; + + if (contractMatches?.length) { + contractAddresses = contractMatches.map(text => text.split(ADDRESS_REGEXP)[1]); + descriptionAddresses = addressMatches?.filter(address => !contractAddresses.includes(address)) || []; + } + + const addresses = extractAddresses(translateData); + + const descriptionSummaryValues = createAddressSummaryValues(descriptionAddresses, addresses); + const contractSummaryValues = createAddressSummaryValues(contractAddresses, addresses, true); + + const summaryValues = [ ...descriptionSummaryValues, ...contractSummaryValues ]; + + return summaryValues; +}; + +const createAddressSummaryValues = (descriptionAddresses: Array, addresses: Array, isContract = false) => { + const summaryValues: Array = descriptionAddresses.map(match => { + const address = addresses.find(address => address.hash.toUpperCase().startsWith(match.toUpperCase())); + + if (!address) { + return undefined; + } + + const value: SummaryValues = { + match: match, + type: 'address', + value: isContract ? { ...address, is_contract: true } : address, + }; + + return value; + }); + + return summaryValues.filter(value => value !== undefined) as Array; +}; + +function extractAddresses(data: NovesResponseData) { + const addressesSet: Set<{ hash: string | null; name?: string | null }> = new Set(); // Use a Set to store unique addresses + + addressesSet.add({ hash: data.rawTransactionData.fromAddress }); + addressesSet.add({ hash: data.rawTransactionData.toAddress }); + + if (data.classificationData.approved) { + addressesSet.add({ hash: data.classificationData.approved.spender }); + } + + if (data.classificationData.deployedContractAddress) { + addressesSet.add({ hash: data.classificationData.deployedContractAddress }); + } + + if (data.txTypeVersion === 2) { + data.classificationData.sent.forEach((transaction) => { + addressesSet.add({ hash: transaction.from.address, name: transaction.from.name }); + addressesSet.add({ hash: transaction.to.address, name: transaction.to.name }); + }); + + data.classificationData.received.forEach((transaction) => { + addressesSet.add({ hash: transaction.from.address, name: transaction.from.name }); + addressesSet.add({ hash: transaction.to.address, name: transaction.to.name }); + }); + } + + const addresses = Array.from(addressesSet) as Array<{hash: string; name?: string}>; // Convert Set to an array + + // Remove empty and null values + return addresses.filter(address => address.hash !== null && address.hash !== '' && address.hash !== undefined); +} diff --git a/ui/tx/assetFlows/utils/getTokensData.test.ts b/ui/tx/assetFlows/utils/getTokensData.test.ts new file mode 100644 index 0000000000..37124e000c --- /dev/null +++ b/ui/tx/assetFlows/utils/getTokensData.test.ts @@ -0,0 +1,9 @@ +import * as transactionMock from 'mocks/noves/transaction'; + +import { getTokensData } from './getTokensData'; + +it('creates a tokens data object', async() => { + const result = getTokensData(transactionMock.transaction); + + expect(result).toEqual(transactionMock.tokenData); +}); diff --git a/ui/tx/assetFlows/utils/getTokensData.ts b/ui/tx/assetFlows/utils/getTokensData.ts new file mode 100644 index 0000000000..2b14c9231c --- /dev/null +++ b/ui/tx/assetFlows/utils/getTokensData.ts @@ -0,0 +1,80 @@ +import _ from 'lodash'; + +import type { NovesResponseData } from 'types/api/noves'; +import type { TokenInfo } from 'types/api/token'; + +import { HEX_REGEXP } from 'lib/regexp'; + +export interface NovesTokenInfo extends Pick { + id?: string | undefined; +} + +export interface TokensData { + nameList: Array; + symbolList: Array; + idList: Array; + byName: { + [x: string]: NovesTokenInfo; + }; + bySymbol: { + [x: string]: NovesTokenInfo; + }; +} + +export function getTokensData(data: NovesResponseData): TokensData { + const sent = data.classificationData.sent || []; + const received = data.classificationData.received || []; + const approved = data.classificationData.approved ? [ data.classificationData.approved ] : []; + + const txItems = [ ...sent, ...received, ...approved ]; + + // Extract all tokens data + const tokens = txItems.map((item) => { + const name = item.nft?.name || item.token?.name || null; + const symbol = item.nft?.symbol || item.token?.symbol || null; + const address = item.nft?.address || item.token?.address || ''; + + const validTokenAddress = address ? HEX_REGEXP.test(address) : false; + + const token = { + name: name, + symbol: symbol?.toLowerCase() === name?.toLowerCase() ? null : symbol, + address: validTokenAddress ? address : '', + id: item.nft?.id || item.token?.id, + }; + + return token; + }); + + // Group tokens by property into arrays + const tokensGroupByname = _.groupBy(tokens, 'name'); + const tokensGroupBySymbol = _.groupBy(tokens, 'symbol'); + const tokensGroupById = _.groupBy(tokens, 'id'); + + // Map properties to an object and remove duplicates + const mappedNames = _.mapValues(tokensGroupByname, (i) => { + return i[0]; + }); + + const mappedSymbols = _.mapValues(tokensGroupBySymbol, (i) => { + return i[0]; + }); + + const mappedIds = _.mapValues(tokensGroupById, (i) => { + return i[0]; + }); + + const filters = [ 'undefined', 'null' ]; + // Array of keys to match in string + const nameList = _.keysIn(mappedNames).filter(i => !filters.includes(i)); + const symbolList = _.keysIn(mappedSymbols).filter(i => !filters.includes(i)); + const idList = _.keysIn(mappedIds).filter(i => !filters.includes(i)); + + return { + nameList, + symbolList, + idList, + byName: mappedNames, + bySymbol: mappedSymbols, + }; +} diff --git a/ui/tx/details/TxDetailsTokenTransfer.tsx b/ui/tx/details/TxDetailsTokenTransfer.tsx index 43bfbde8d2..5c5762b184 100644 --- a/ui/tx/details/TxDetailsTokenTransfer.tsx +++ b/ui/tx/details/TxDetailsTokenTransfer.tsx @@ -1,13 +1,19 @@ -import { Flex, chakra } from '@chakra-ui/react'; +import { Flex } from '@chakra-ui/react'; import React from 'react'; -import type { TokenTransfer as TTokenTransfer, Erc20TotalPayload, Erc721TotalPayload, Erc1155TotalPayload } from 'types/api/tokenTransfer'; +import type { + TokenTransfer as TTokenTransfer, + Erc20TotalPayload, + Erc721TotalPayload, + Erc1155TotalPayload, + Erc404TotalPayload, +} from 'types/api/tokenTransfer'; -import getCurrencyValue from 'lib/getCurrencyValue'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; -import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import NftTokenTransferSnippet from 'ui/tx/NftTokenTransferSnippet'; +import FtTokenTransferSnippet from '../FtTokenTransferSnippet'; + interface Props { data: TTokenTransfer; } @@ -18,26 +24,7 @@ const TxDetailsTokenTransfer = ({ data }: Props) => { switch (data.token.type) { case 'ERC-20': { const total = data.total as Erc20TotalPayload; - const { valueStr, usd } = getCurrencyValue({ - value: total.value, - exchangeRate: data.token.exchange_rate, - accuracyUsd: 2, - decimals: total.decimals, - }); - - return ( - <> - for - { valueStr } - - { usd && (${ usd }) } - - ); + return ; } case 'ERC-721': { @@ -62,6 +49,26 @@ const TxDetailsTokenTransfer = ({ data }: Props) => { /> ); } + + case 'ERC-404': { + const total = data.total as Erc404TotalPayload; + + if (total.token_id !== null) { + return ( + + ); + } else { + if (total.value === null) { + return null; + } + + return ; + } + } } })(); diff --git a/ui/tx/details/TxInfo.tsx b/ui/tx/details/TxInfo.tsx index 204d1aa24e..f038a278a8 100644 --- a/ui/tx/details/TxInfo.tsx +++ b/ui/tx/details/TxInfo.tsx @@ -17,6 +17,7 @@ import { scroller, Element } from 'react-scroll'; import type { Transaction } from 'types/api/transaction'; import { ZKEVM_L2_TX_STATUSES } from 'types/api/transaction'; +import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; import { route } from 'nextjs-routes'; @@ -54,6 +55,7 @@ import TxDetailsWithdrawalStatus from 'ui/tx/details/TxDetailsWithdrawalStatus'; import TxRevertReason from 'ui/tx/details/TxRevertReason'; import TxAllowedPeekers from 'ui/tx/TxAllowedPeekers'; import TxSocketAlert from 'ui/tx/TxSocketAlert'; +import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo'; const rollupFeature = config.features.rollup; @@ -143,7 +145,11 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { ) } @@ -192,6 +198,15 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { ) } + { data.zksync && ( + + + + ) } { /> ) } + { data.zksync && ( + + { data.zksync.batch_number ? ( + + ) : Pending } + + ) } { data.timestamp && ( { ) } + { data.zksync && } ) } diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png index 14b7765c09..c8d0a04f44 100644 Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png differ diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png index 0c959f9478..d4476b8a86 100644 Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png differ diff --git a/ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchDetails.tsx b/ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchDetails.tsx new file mode 100644 index 0000000000..8505177d25 --- /dev/null +++ b/ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchDetails.tsx @@ -0,0 +1,180 @@ +import { Grid, GridItem, Link, Skeleton, Text } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import BigNumber from 'bignumber.js'; +import { useRouter } from 'next/router'; +import React from 'react'; +import { scroller, Element } from 'react-scroll'; + +import { ZKSYNC_L2_TX_BATCH_STATUSES, type ZkSyncBatch } from 'types/api/zkSyncL2'; + +import { route } from 'nextjs-routes'; + +import type { ResourceError } from 'lib/api/resources'; +import { WEI, WEI_IN_GWEI } from 'lib/consts'; +import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; +import { currencyUnits } from 'lib/units'; +import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; +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 DetailsTimestamp from 'ui/shared/DetailsTimestamp'; +import LinkInternal from 'ui/shared/LinkInternal'; +import PrevNext from 'ui/shared/PrevNext'; +import TruncatedValue from 'ui/shared/TruncatedValue'; +import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; + +import ZkSyncL2TxnBatchHashesInfo from './ZkSyncL2TxnBatchHashesInfo'; + +interface Props { + query: UseQueryResult; +} + +const ZkSyncL2TxnBatchDetails = ({ query }: Props) => { + const router = useRouter(); + const [ isExpanded, setIsExpanded ] = React.useState(false); + + const { data, isPlaceholderData, isError, error } = query; + + const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => { + if (!data) { + return; + } + + const increment = direction === 'next' ? +1 : -1; + const nextId = String(data.number + increment); + + router.push({ pathname: '/batches/[number]', query: { number: nextId } }, undefined); + }, [ data, router ]); + + const handleCutClick = React.useCallback(() => { + setIsExpanded((flag) => !flag); + scroller.scrollTo('ZkSyncL2TxnBatchDetails__cutLink', { + duration: 500, + smooth: true, + }); + }, []); + + if (isError) { + if (isCustomAppError(error)) { + throwOnResourceLoadError({ isError, error }); + } + + return ; + } + + if (!data) { + return null; + } + + const txNum = data.l2_tx_count + data.l1_tx_count; + + return ( + + + + { data.number } + + + + + + + + + + { data.timestamp ? : 'Undefined' } + + + + + + { txNum } transaction{ txNum === 1 ? '' : 's' } + + + + + + + + + + + + + { isExpanded ? 'Hide details' : 'View details' } + + + + + + { isExpanded && ( + <> + + + + + + + + + { BigNumber(data.l1_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether } + ({ BigNumber(data.l1_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei }) + + + + { BigNumber(data.l2_fair_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether } + ({ BigNumber(data.l2_fair_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei }) + + + ) } + + ); +}; + +export default ZkSyncL2TxnBatchDetails; diff --git a/ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo.tsx b/ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo.tsx new file mode 100644 index 0000000000..bb0bfcb80d --- /dev/null +++ b/ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo.tsx @@ -0,0 +1,101 @@ +import { Flex, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import type { ZkSyncBatch } from 'types/api/zkSyncL2'; + +import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; +import DetailsTimestamp from 'ui/shared/DetailsTimestamp'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; + +interface Props { + isLoading: boolean; + data: Pick< + ZkSyncBatch, + 'commit_transaction_hash' | + 'commit_transaction_timestamp' | + 'prove_transaction_hash' | + 'prove_transaction_timestamp' | + 'execute_transaction_hash' | + 'execute_transaction_timestamp' + >; +} + +const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => { + return ( + <> + + { data.commit_transaction_hash ? ( + <> + + { data.commit_transaction_timestamp && ( + + + + ) } + + ) : Pending } + + + + { data.prove_transaction_hash ? ( + <> + + { data.prove_transaction_timestamp && ( + + + + ) } + + ) : Pending } + + + + { data.execute_transaction_hash ? ( + <> + + { data.execute_transaction_timestamp && ( + + + + ) } + + ) : Pending } + + + ); +}; + +export default React.memo(ZkSyncL2TxnBatchHashesInfo); diff --git a/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesListItem.tsx b/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesListItem.tsx new file mode 100644 index 0000000000..d930604763 --- /dev/null +++ b/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesListItem.tsx @@ -0,0 +1,94 @@ +import { Skeleton, Text } from '@chakra-ui/react'; +import React from 'react'; + +import type { ZkSyncBatchesItem } from 'types/api/zkSyncL2'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; +import dayjs from 'lib/date/dayjs'; +import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; +import LinkInternal from 'ui/shared/LinkInternal'; +import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; +import ZkSyncL2TxnBatchStatus from 'ui/shared/statusTag/ZkSyncL2TxnBatchStatus'; + +const rollupFeature = config.features.rollup; + +type Props = { item: ZkSyncBatchesItem; isLoading?: boolean }; + +const ZkSyncTxnBatchesListItem = ({ item, isLoading }: Props) => { + const timeAgo = item.timestamp ? dayjs(item.timestamp).fromNow() : 'Undefined'; + + if (!rollupFeature.isEnabled || rollupFeature.type !== 'zkSync') { + return null; + } + + return ( + + + Batch # + + + + + Status + + + + + Age + + { timeAgo } + + + Txn count + + + + { item.tx_count } + + + + + Commit tx + + { item.commit_transaction_hash ? ( + + ) : Pending } + + + Prove tx + + { item.prove_transaction_hash ? ( + + ) : Pending } + + + + ); +}; + +export default ZkSyncTxnBatchesListItem; diff --git a/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTable.tsx b/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTable.tsx new file mode 100644 index 0000000000..d96754466f --- /dev/null +++ b/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTable.tsx @@ -0,0 +1,42 @@ +import { Table, Tbody, Th, Tr } from '@chakra-ui/react'; +import React from 'react'; + +import type { ZkSyncBatchesItem } from 'types/api/zkSyncL2'; + +import { default as Thead } from 'ui/shared/TheadSticky'; + +import ZkSyncTxnBatchesTableItem from './ZkSyncTxnBatchesTableItem'; + +type Props = { + items: Array; + top: number; + isLoading?: boolean; +} + +const ZkSyncTxnBatchesTable = ({ items, top, isLoading }: Props) => { + return ( +
AppContracts scoreTotalVerified
+ + : + + } + /> + + + + + + + + { securityReport?.overallInfo.totalContractsNumber ?? 0 } + + + + { securityReport?.overallInfo.verifiedNumber ?? 0 } + + + Data will be available soon + + +
TokenToken Token IDTxn hashFrom/To - - + + { token.type } { getTokenTransferTypeText(type) } diff --git a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png index 4bbe99bcfb..c372d67923 100644 Binary files a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png and b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png differ diff --git a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png index 51fc3312d5..4a5f12f4e8 100644 Binary files a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png and b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png differ diff --git a/ui/shared/Web3ModalProvider.tsx b/ui/shared/Web3ModalProvider.tsx index fc2f8553b7..6dd8be6149 100644 --- a/ui/shared/Web3ModalProvider.tsx +++ b/ui/shared/Web3ModalProvider.tsx @@ -1,58 +1,38 @@ import { useColorMode } from '@chakra-ui/react'; -import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc'; -import { createWeb3Modal, useWeb3ModalTheme, defaultWagmiConfig } from '@web3modal/wagmi/react'; +import { createWeb3Modal, useWeb3ModalTheme } from '@web3modal/wagmi/react'; import React from 'react'; -import { configureChains, WagmiConfig } from 'wagmi'; +import { WagmiProvider } from 'wagmi'; import config from 'configs/app'; -import currentChain from 'lib/web3/currentChain'; +import wagmiConfig from 'lib/web3/wagmiConfig'; import colors from 'theme/foundations/colors'; import { BODY_TYPEFACE } from 'theme/foundations/typography'; import zIndices from 'theme/foundations/zIndices'; const feature = config.features.blockchainInteraction; -const getConfig = () => { +const init = () => { try { - if (!feature.isEnabled) { - throw new Error(); + if (!wagmiConfig || !feature.isEnabled) { + return; } - const { chains } = configureChains( - [ currentChain ], - [ - jsonRpcProvider({ - rpc: () => ({ - http: config.chain.rpcUrl || '', - }), - }), - ], - ); - - 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, }, + featuredWalletIds: [], + allowUnsupportedChain: true, }); - - return { wagmiConfig }; - } catch (error) { - return { }; - } + } catch (error) {} }; -const { wagmiConfig } = getConfig(); +init(); interface Props { children: React.ReactNode; @@ -77,9 +57,9 @@ const Provider = ({ children, fallback }: Props) => { } return ( - + { children } - + ); }; diff --git a/ui/shared/ad/AdBanner.tsx b/ui/shared/ad/AdBanner.tsx index fa22442ba3..fcad0eedbc 100644 --- a/ui/shared/ad/AdBanner.tsx +++ b/ui/shared/ad/AdBanner.tsx @@ -14,14 +14,16 @@ import SliseBanner from './SliseBanner'; const feature = config.features.adsBanner; const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: boolean }) => { + const provider = useAppContext().adBannerProvider; + const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); - if (!feature.isEnabled || hasAdblockCookie) { + if (!feature.isEnabled || hasAdblockCookie || !provider) { return null; } const content = (() => { - switch (feature.provider) { + switch (provider) { case 'adbutler': return ; case 'coinzilla': @@ -40,7 +42,7 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo className={ className } isLoaded={ !isLoading } borderRadius="none" - maxW={ feature.provider === 'adbutler' ? feature.adButler.config.desktop.width : '728px' } + maxW={ ('adButler' in feature && feature.adButler) ? feature.adButler.config.desktop.width : '728px' } w="100%" > { content } diff --git a/ui/shared/ad/AdbutlerBanner.tsx b/ui/shared/ad/AdbutlerBanner.tsx index b6f5f6e6bd..aa6db55ea4 100644 --- a/ui/shared/ad/AdbutlerBanner.tsx +++ b/ui/shared/ad/AdbutlerBanner.tsx @@ -13,8 +13,9 @@ const feature = config.features.adsBanner; const AdbutlerBanner = ({ className }: { className?: string }) => { const router = useRouter(); const isMobile = useIsMobile(); + React.useEffect(() => { - if (!feature.isEnabled || feature.provider !== 'adbutler') { + if (!('adButler' in feature)) { return; } diff --git a/ui/shared/ad/adbutlerScript.ts b/ui/shared/ad/adbutlerScript.ts index 6228a231bf..81b057e679 100644 --- a/ui/shared/ad/adbutlerScript.ts +++ b/ui/shared/ad/adbutlerScript.ts @@ -8,7 +8,7 @@ export const connectAdbutler = `if (!window.AdButler){(function(){var s = docume export const placeAd = (() => { const feature = config.features.adsBanner; - if (!feature.isEnabled || feature.provider !== 'adbutler') { + if (!('adButler' in feature)) { return; } diff --git a/ui/shared/entities/address/AddressEntity.tsx b/ui/shared/entities/address/AddressEntity.tsx index 5a29747fca..08d043ffe4 100644 --- a/ui/shared/entities/address/AddressEntity.tsx +++ b/ui/shared/entities/address/AddressEntity.tsx @@ -28,8 +28,9 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Pick & { +type IconProps = Omit & Pick & { asProp?: As; + name?: EntityBase.IconBaseProps['name']; }; const Icon = (props: IconProps) => { @@ -159,7 +160,7 @@ const AddressEntry = (props: EntityProps) => { onMouseLeave={ context?.onMouseLeave } position="relative" > - + diff --git a/ui/shared/entities/address/AddressIdenticon.tsx b/ui/shared/entities/address/AddressIdenticon.tsx index bdefbf66d2..a08fa5a4a9 100644 --- a/ui/shared/entities/address/AddressIdenticon.tsx +++ b/ui/shared/entities/address/AddressIdenticon.tsx @@ -3,6 +3,7 @@ import dynamic from 'next/dynamic'; import React from 'react'; import config from 'configs/app'; +import * as cookies from 'lib/cookies'; import IdenticonGithub from 'ui/shared/IdenticonGithub'; interface IconProps { @@ -12,7 +13,8 @@ interface IconProps { const Icon = dynamic( async() => { - switch (config.UI.views.address.identiconType) { + const type = cookies.get(cookies.NAMES.ADDRESS_IDENTICON_TYPE) || config.UI.views.address.identiconType; + switch (type) { case 'github': { // eslint-disable-next-line react/display-name return (props: IconProps) => ; diff --git a/ui/shared/entities/base/components.tsx b/ui/shared/entities/base/components.tsx index 186eb76f9f..ef2447f001 100644 --- a/ui/shared/entities/base/components.tsx +++ b/ui/shared/entities/base/components.tsx @@ -18,6 +18,7 @@ export type Truncation = 'constant' | 'constant_long' | 'dynamic' | 'tail' | 'no export interface EntityBaseProps { className?: string; href?: string; + iconName?: IconName; iconSize?: IconSize; iconColor?: IconProps['color']; isExternal?: boolean; @@ -80,13 +81,13 @@ const Link = chakra(({ isLoading, children, isExternal, onClick, href, noLink }: ); }); -export interface IconBaseProps extends Pick { +export interface IconBaseProps extends Pick { name: IconName; color?: IconProps['color']; borderRadius?: IconProps['borderRadius']; } -const Icon = ({ isLoading, iconSize, noIcon, name, iconColor, color, borderRadius }: IconBaseProps) => { +const Icon = ({ isLoading, iconSize, noIcon, name, color, borderRadius }: IconBaseProps) => { const defaultColor = useColorModeValue('gray.500', 'gray.400'); if (noIcon) { @@ -102,7 +103,7 @@ const Icon = ({ isLoading, iconSize, noIcon, name, iconColor, color, borderRadiu borderRadius={ borderRadius ?? 'base' } display="block" mr={ 2 } - color={ iconColor ?? color ?? defaultColor } + color={ color ?? defaultColor } minW={ 0 } flexShrink={ 0 } /> diff --git a/ui/shared/entities/tx/TxEntity.tsx b/ui/shared/entities/tx/TxEntity.tsx index 796d305ded..4a9b727cae 100644 --- a/ui/shared/entities/tx/TxEntity.tsx +++ b/ui/shared/entities/tx/TxEntity.tsx @@ -71,7 +71,7 @@ const TxEntity = (props: EntityProps) => { return ( - + diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png index aeef08c036..706a4063b9 100644 Binary files a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png index d7ce8e6860..137ece3ca4 100644 Binary files a/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png index 67ab4e360c..26ac426439 100644 Binary files a/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_mobile_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_mobile_base-view-mobile-1.png index 163711c697..cf60a635ee 100644 Binary files a/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_mobile_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/LayoutError.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png index 524992497d..2e478ad946 100644 Binary files a/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_mobile_base-view-mobile-1.png b/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_mobile_base-view-mobile-1.png index b433ff9b54..769ba157b3 100644 Binary files a/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_mobile_base-view-mobile-1.png and b/ui/shared/layout/__screenshots__/LayoutHome.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/shared/layout/components/Content.tsx b/ui/shared/layout/components/Content.tsx index 4f6ddf23c1..e17fcd8397 100644 --- a/ui/shared/layout/components/Content.tsx +++ b/ui/shared/layout/components/Content.tsx @@ -8,7 +8,7 @@ interface Props { const Content = ({ children, className }: Props) => { return ( - + { children } ); diff --git a/ui/shared/radioButtonGroup/RadioButtonGroup.tsx b/ui/shared/radioButtonGroup/RadioButtonGroup.tsx index e4eb0b2358..f110ef542f 100644 --- a/ui/shared/radioButtonGroup/RadioButtonGroup.tsx +++ b/ui/shared/radioButtonGroup/RadioButtonGroup.tsx @@ -9,6 +9,7 @@ type RadioItemProps = { title: string; icon?: IconName; onlyIcon: false | undefined; + contentAfter?: React.ReactNode; } | { title: string; icon: IconName; @@ -67,9 +68,11 @@ const RadioButton = (props: RadioButtonProps) => { > { props.title } + { props.contentAfter } ); @@ -80,15 +83,22 @@ type RadioButtonGroupProps = { name: string; defaultValue: string; options: Array<{ value: T } & RadioItemProps>; + autoWidth?: boolean; } -const RadioButtonGroup = ({ onChange, name, defaultValue, options }: RadioButtonGroupProps) => { +const RadioButtonGroup = ({ onChange, name, defaultValue, options, autoWidth = false }: RadioButtonGroupProps) => { const { getRootProps, getRadioProps } = useRadioGroup({ name, defaultValue, onChange }); const group = getRootProps(); return ( - + { options.map((option) => { const props = getRadioProps({ value: option.value }); return ; diff --git a/ui/shared/solidityscanReport/SolidityscanReportButton.tsx b/ui/shared/solidityscanReport/SolidityscanReportButton.tsx new file mode 100644 index 0000000000..d5c43f107f --- /dev/null +++ b/ui/shared/solidityscanReport/SolidityscanReportButton.tsx @@ -0,0 +1,46 @@ +import { Button, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import useScoreLevelAndColor from './useScoreLevelAndColor'; + +interface Props { + className?: string; + score: number; + isLoading?: boolean; + height?: string; + onlyIcon?: boolean; + onClick?: () => void; +} + +const SolidityscanReportButton = ( + { className, score, isLoading, height = '32px', onlyIcon, onClick }: Props, + ref: React.ForwardedRef, +) => { + const { scoreColor } = useScoreLevelAndColor(score); + + return ( + + + + ); +}; + +export default React.forwardRef(SolidityscanReportButton); diff --git a/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx b/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx new file mode 100644 index 0000000000..2d2a5cf140 --- /dev/null +++ b/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx @@ -0,0 +1,60 @@ +import { Box, Flex, Text, Grid, useColorModeValue, chakra } from '@chakra-ui/react'; +import React from 'react'; + +import type { SolidityscanReport } from 'types/api/contract'; + +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 { + vulnerabilities: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + vulnerabilitiesCount: number; +} + +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 SolidityscanReportDetails = ({ vulnerabilities, vulnerabilitiesCount }: Props) => { + return ( + + { DISTRIBUTION_ITEMS.map(item => ( + + )) } + + ); +}; + +export default chakra(SolidityscanReportDetails); diff --git a/ui/shared/solidityscanReport/SolidityscanReportScore.tsx b/ui/shared/solidityscanReport/SolidityscanReportScore.tsx new file mode 100644 index 0000000000..56bd296630 --- /dev/null +++ b/ui/shared/solidityscanReport/SolidityscanReportScore.tsx @@ -0,0 +1,45 @@ +import { Box, Flex, Text, chakra, Center, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import useScoreLevelAndColor from './useScoreLevelAndColor'; + +interface Props { + className?: string; + score: number; +} + +const SolidityscanReportScore = ({ className, score }: Props) => { + const { scoreLevel, scoreColor } = useScoreLevelAndColor(score); + + const chartGrayColor = useColorModeValue('gray.100', 'gray.700'); + const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); + const popoverBgColor = useColorModeValue('white', 'gray.900'); + + return ( + + +
+ +
+
+ + + { score } + / 100 + + Security score is { scoreLevel } + +
+ ); +}; + +export default chakra(SolidityscanReportScore); diff --git a/ui/shared/solidityscanReport/useScoreLevelAndColor.ts b/ui/shared/solidityscanReport/useScoreLevelAndColor.ts new file mode 100644 index 0000000000..b61d2a9109 --- /dev/null +++ b/ui/shared/solidityscanReport/useScoreLevelAndColor.ts @@ -0,0 +1,21 @@ +import { useColorModeValue } from '@chakra-ui/react'; + +export default function useScoreLevelAndColor(score: number) { + const greatScoreColor = useColorModeValue('green.600', 'green.400'); + const averageScoreColor = useColorModeValue('purple.600', 'purple.400'); + const lowScoreColor = useColorModeValue('red.600', 'red.400'); + + let scoreColor; + let scoreLevel; + if (score >= 80) { + scoreColor = greatScoreColor; + scoreLevel = 'GREAT'; + } else if (score >= 30) { + scoreColor = averageScoreColor; + scoreLevel = 'AVERAGE'; + } else { + scoreColor = lowScoreColor; + scoreLevel = 'LOW'; + } + return { scoreColor, scoreLevel }; +} diff --git a/ui/shared/stats/StatsWidget.tsx b/ui/shared/stats/StatsWidget.tsx index f95220bac9..53dcbbd312 100644 --- a/ui/shared/stats/StatsWidget.tsx +++ b/ui/shared/stats/StatsWidget.tsx @@ -14,7 +14,7 @@ type Props = { } const StatsWidget = ({ label, value, isLoading, hint, diff, diffPeriod = '24h', diffFormatted }: Props) => { - const bgColor = useColorModeValue('blue.50', 'blue.800'); + const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100'); const skeletonBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const hintColor = useColorModeValue('gray.600', 'gray.400'); diff --git a/ui/shared/statusTag/ZkSyncL2TxnBatchStatus.tsx b/ui/shared/statusTag/ZkSyncL2TxnBatchStatus.tsx new file mode 100644 index 0000000000..7e0536eff4 --- /dev/null +++ b/ui/shared/statusTag/ZkSyncL2TxnBatchStatus.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import type { ZkSyncBatchStatus } from 'types/api/zkSyncL2'; + +import type { StatusTagType } from './StatusTag'; +import StatusTag from './StatusTag'; + +export interface Props { + status: ZkSyncBatchStatus; + isLoading?: boolean; +} + +const ZkSyncL2TxnBatchStatus = ({ status, isLoading }: Props) => { + let type: StatusTagType; + + switch (status) { + case 'Executed on L1': + type = 'ok'; + break; + default: + type = 'pending'; + break; + } + + return ; +}; + +export default ZkSyncL2TxnBatchStatus; diff --git a/ui/shared/userOps/UserOpSponsorType.tsx b/ui/shared/userOps/UserOpSponsorType.tsx index cd8d27327d..cc006ffdef 100644 --- a/ui/shared/userOps/UserOpSponsorType.tsx +++ b/ui/shared/userOps/UserOpSponsorType.tsx @@ -1,10 +1,10 @@ import { Tag } from '@chakra-ui/react'; import React from 'react'; -import { UserOpSponsorType } from 'types/api/userOps'; +import type { UserOpSponsorType as TUserOpSponsorType } from 'types/api/userOps'; type Props = { - sponsorType: UserOpSponsorType; + sponsorType: TUserOpSponsorType; } const UserOpSponsorType = ({ sponsorType }: Props) => { diff --git a/ui/snippets/footer/Footer.pw.tsx b/ui/snippets/footer/Footer.pw.tsx index 5749ebf485..a3dc45f5c4 100644 --- a/ui/snippets/footer/Footer.pw.tsx +++ b/ui/snippets/footer/Footer.pw.tsx @@ -1,6 +1,7 @@ import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import type { WindowProvider } from 'wagmi'; + +import type { WalletProvider } from 'types/web3'; import { buildExternalAssetFilePath } from 'configs/app/utils'; import { FOOTER_LINKS } from 'mocks/config/footerLinks'; @@ -36,7 +37,7 @@ base.describe('with custom links, max cols', () => { window.ethereum = { isMetaMask: true, _events: {}, - } as WindowProvider; + } as WalletProvider; }); await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({ @@ -95,7 +96,7 @@ base.describe('without custom links', () => { window.ethereum = { isMetaMask: true, _events: {}, - } as WindowProvider; + } as WalletProvider; }); await page.route(BACKEND_VERSION_API_URL, (route) => { return route.fulfill({ @@ -118,7 +119,7 @@ base.describe('without custom links', () => { await page.evaluate(() => { window.ethereum = { providers: [ { isMetaMask: true, _events: {} } ], - } as WindowProvider; + } as WalletProvider; }); await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({ diff --git a/ui/snippets/footer/Footer.tsx b/ui/snippets/footer/Footer.tsx index 4c982f40ca..52bb5cc57b 100644 --- a/ui/snippets/footer/Footer.tsx +++ b/ui/snippets/footer/Footer.tsx @@ -50,9 +50,9 @@ const Footer = () => { url: 'https://github.com/blockscout/blockscout', }, { - icon: 'social/tweet' as const, + icon: 'social/twitter' as const, iconSize: '18px', - text: 'Twitter', + text: 'X (ex-Twitter)', url: 'https://www.twitter.com/blockscoutcom', }, { 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 0fc5ef3bc9..cee8353482 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 ca21b198bd..2dc7fee18d 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 8e7ccaa17f..c6fead1104 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 30756d332a..7d72b3cce6 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 4ed0980dbd..0a7fd22261 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 252ba0ea84..1131de2504 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 a8b838f541..44c4be8454 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 aada6b053a..d93aa584d9 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 5681129337..6987a4f229 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 edab8c4966..3c6775e03a 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 483b96df6c..22f73abe34 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 ba074854d2..1214d88ab0 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 c519578200..ccae54fcc4 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/__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 index 20aa839385..bf29251fe5 100644 Binary files a/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_dark-color-mode_default-view-dark-mode-1.png 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 index d143733147..66b62ef072 100644 Binary files a/ui/snippets/header/__screenshots__/HeaderDesktop.pw.tsx_default_default-view-dark-mode-1.png 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 index 01dba03498..f3d92fa5d8 100644 Binary files a/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_dark-color-mode_default-view-dark-mode-1.png 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 index 803a3f79ec..526388a465 100644 Binary files a/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_default_default-view-dark-mode-1.png and b/ui/snippets/header/__screenshots__/HeaderMobile.pw.tsx_default_default-view-dark-mode-1.png differ diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx index 858c26f729..381777ecb2 100644 --- a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx +++ b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx @@ -1,59 +1,37 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as profileMock from 'mocks/user/profile'; import authFixture from 'playwright/fixtures/auth'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import * as app from 'playwright/utils/app'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; import ProfileMenuDesktop from './ProfileMenuDesktop'; -test('no auth', async({ mount, page }) => { +test('no auth', async({ render, page }) => { const hooksConfig = { router: { asPath: '/', pathname: '/', }, }; - const component = await mount( - - - , - { hooksConfig }, - ); - + const component = await render(, { hooksConfig }); await component.locator('a').click(); + expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`); }); -test.describe('auth', () => { - const extendedTest = test.extend({ - context: ({ context }, use) => { - authFixture(context); - use(context); - }, - }); - - extendedTest('+@dark-mode', async({ mount, page }) => { - await page.route(buildApiUrl('user_info'), (route) => route.fulfill({ - status: 200, - body: JSON.stringify(profileMock.base), - })); - await page.route(profileMock.base.avatar, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_s.jpg', - }); - }); +const authTest = test.extend({ + context: ({ context }, use) => { + authFixture(context); + use(context); + }, +}); +authTest('auth +@dark-mode', async({ render, page, mockApiResponse, mockAssetResponse }) => { + await mockApiResponse('user_info', profileMock.base); + await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg'); - const component = await mount( - - - , - ); + const component = await render(); + await component.getByAltText(/Profile picture/i).click(); - await component.getByAltText(/Profile picture/i).click(); - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } }); - }); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } }); }); diff --git a/ui/snippets/searchBar/SearchBar.tsx b/ui/snippets/searchBar/SearchBar.tsx index 362f2e7639..9ceee88101 100644 --- a/ui/snippets/searchBar/SearchBar.tsx +++ b/ui/snippets/searchBar/SearchBar.tsx @@ -1,4 +1,14 @@ -import { Box, Portal, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, PopoverFooter, useOutsideClick } from '@chakra-ui/react'; +import { + Box, + Portal, + Popover, + PopoverTrigger, + PopoverContent, + PopoverBody, + PopoverFooter, + useDisclosure, + useOutsideClick, +} from '@chakra-ui/react'; import _debounce from 'lodash/debounce'; import { useRouter } from 'next/router'; import type { FormEvent } from 'react'; @@ -12,6 +22,7 @@ import * as mixpanel from 'lib/mixpanel/index'; import { getRecentSearchKeywords, saveToRecentKeywords } from 'lib/recentSearchKeywords'; import LinkInternal from 'ui/shared/LinkInternal'; +import SearchBarBackdrop from './SearchBarBackdrop'; import SearchBarInput from './SearchBarInput'; import SearchBarRecentKeywords from './SearchBarRecentKeywords'; import SearchBarSuggest from './SearchBarSuggest/SearchBarSuggest'; @@ -106,69 +117,73 @@ const SearchBar = ({ isHomepage }: Props) => { }, [ calculateMenuWidth ]); return ( - 0 || recentSearchKeywords.length > 0) } - autoFocus={ false } - onClose={ onClose } - placement="bottom-start" - offset={ isMobile && !isHomepage ? [ 16, -4 ] : undefined } - isLazy - > - - - - - - + 0 || recentSearchKeywords.length > 0) } + autoFocus={ false } + onClose={ onClose } + placement="bottom-start" + offset={ isMobile && !isHomepage ? [ 16, -4 ] : undefined } + isLazy + > + + + + + - - { searchTerm.trim().length === 0 && recentSearchKeywords.length > 0 && ( - - ) } - { searchTerm.trim().length > 0 && ( - - ) } - - - { searchTerm.trim().length > 0 && query.data && query.data.length >= 50 && ( - - - View all results - - - ) } - - - + { 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/SearchBarBackdrop.tsx b/ui/snippets/searchBar/SearchBarBackdrop.tsx new file mode 100644 index 0000000000..05d2702fe8 --- /dev/null +++ b/ui/snippets/searchBar/SearchBarBackdrop.tsx @@ -0,0 +1,25 @@ +import { Box, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +interface Props { + isOpen: boolean; +} + +const SearchBarBackdrop = ({ isOpen }: Props) => { + const backdropBgColor = useColorModeValue('blackAlpha.400', 'blackAlpha.600'); + + return ( + + ); +}; + +export default React.memo(SearchBarBackdrop); diff --git a/ui/snippets/searchBar/SearchBarInput.tsx b/ui/snippets/searchBar/SearchBarInput.tsx index d1eb066474..d4088e06a8 100644 --- a/ui/snippets/searchBar/SearchBarInput.tsx +++ b/ui/snippets/searchBar/SearchBarInput.tsx @@ -16,10 +16,14 @@ interface Props { onHide?: () => void; onClear: () => void; isHomepage?: boolean; + isSuggestOpen?: boolean; value: string; } -const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHide, onClear, value }: Props, ref: React.ForwardedRef) => { +const SearchBarInput = ( + { onChange, onSubmit, isHomepage, isSuggestOpen, 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); @@ -71,10 +75,10 @@ const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHid w="100%" backgroundColor={ bgColor } borderRadius={{ base: isHomepage ? 'base' : 'none', lg: 'base' }} - position={{ base: isHomepage ? 'static' : 'absolute', lg: 'static' }} + position={{ base: isHomepage ? 'static' : 'absolute', lg: 'relative' }} top={{ base: isHomepage ? 0 : 55, lg: 0 }} left="0" - zIndex={{ base: isHomepage ? 'auto' : '-1', lg: 'auto' }} + zIndex={{ base: isHomepage ? 'auto' : '-1', lg: isSuggestOpen ? 'popover' : 'auto' }} paddingX={{ base: isHomepage ? 0 : 4, lg: 0 }} paddingTop={{ base: isHomepage ? 0 : 1, lg: 0 }} paddingBottom={{ base: isHomepage ? 0 : 2, lg: 0 }} diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_recent-keywords-suggest-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_recent-keywords-suggest-mobile-1.png index 9cfc914b85..4bf76fee17 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_recent-keywords-suggest-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_recent-keywords-suggest-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png index 9017b6762c..97875b4515 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-address-hash-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-address-hash-mobile-1.png index 5943a7308a..cb71b52d02 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-address-hash-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-address-hash-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-blob-hash-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-blob-hash-mobile-1.png index 7794b3be18..79f6fbfd8e 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-blob-hash-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-blob-hash-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-hash-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-hash-mobile-1.png index 4814677c87..64ff86056f 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-hash-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-hash-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png index dae3d9949c..b40ca14719 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-contract-name-mobile-dark-mode-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-contract-name-mobile-dark-mode-1.png index 89a811c836..eea4e39708 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-contract-name-mobile-dark-mode-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-contract-name-mobile-dark-mode-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-name-homepage-dark-mode-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-name-homepage-dark-mode-1.png index 548c9271b4..809aa9e90a 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-name-homepage-dark-mode-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-name-homepage-dark-mode-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tag-mobile-dark-mode-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tag-mobile-dark-mode-1.png index 486edadb4f..530ed3685f 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tag-mobile-dark-mode-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tag-mobile-dark-mode-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-token-name-mobile-dark-mode-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-token-name-mobile-dark-mode-1.png index 2bc3b994c7..4584baf91f 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-token-name-mobile-dark-mode-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-token-name-mobile-dark-mode-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tx-hash-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tx-hash-mobile-1.png index b14c0278b9..02c42937f1 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tx-hash-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-tx-hash-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-user-op-hash-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-user-op-hash-mobile-1.png index 73f86b73a5..0a2c600ebc 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-user-op-hash-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-user-op-hash-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-with-view-all-link-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-with-view-all-link-1.png index bcee42809f..c58de52f87 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-with-view-all-link-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-with-view-all-link-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_with-apps-default-view-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_with-apps-default-view-mobile-1.png index 86fa94bf04..dd8fbb7f3a 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_with-apps-default-view-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_with-apps-default-view-mobile-1.png differ diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-blob-hash-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-blob-hash-mobile-1.png index 045ddf43d8..4278a3990d 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-blob-hash-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-blob-hash-mobile-1.png differ diff --git a/ui/snippets/topBar/ColorModeSwitch.tsx b/ui/snippets/topBar/ColorModeSwitch.tsx deleted file mode 100644 index aa7156a1e1..0000000000 --- a/ui/snippets/topBar/ColorModeSwitch.tsx +++ /dev/null @@ -1,98 +0,0 @@ -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 deleted file mode 100644 index a1a8a5b4e4..0000000000 --- a/ui/snippets/topBar/ColorModeSwitchSample.tsx +++ /dev/null @@ -1,54 +0,0 @@ -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 deleted file mode 100644 index d69608ed79..0000000000 --- a/ui/snippets/topBar/ColorModeSwitchTheme.tsx +++ /dev/null @@ -1,56 +0,0 @@ -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 index 5d0d4d284e..5b7702f1e1 100644 --- a/ui/snippets/topBar/TopBar.pw.tsx +++ b/ui/snippets/topBar/TopBar.pw.tsx @@ -30,6 +30,6 @@ test('default view +@dark-mode +@mobile', async({ mount, page }) => { await component.getByText(/\$1\.39/).click(); 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 } }); + await component.getByLabel('User settings').click(); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 400 } }); }); diff --git a/ui/snippets/topBar/TopBar.tsx b/ui/snippets/topBar/TopBar.tsx index 443af1b05b..0b97de2aff 100644 --- a/ui/snippets/topBar/TopBar.tsx +++ b/ui/snippets/topBar/TopBar.tsx @@ -3,7 +3,7 @@ import React from 'react'; import config from 'configs/app'; -import ColorModeSwitch from './ColorModeSwitch'; +import Settings from './settings/Settings'; import SwapButton from './SwapButton'; import TopBarStats from './TopBarStats'; @@ -28,7 +28,7 @@ const TopBar = () => { ) } - +
); 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 index 5a124198ea..ef9ee9ef75 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-1.png 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 index 6cdde6c76b..81a796b202 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png 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 index 2efa6dc26f..5d9431dffd 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-1.png 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 index 6fb7bcd102..05add27866 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png 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 index acbfe60a6f..346904777a 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png 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 index d9e7433445..10916853a2 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/settings/Settings.tsx b/ui/snippets/topBar/settings/Settings.tsx new file mode 100644 index 0000000000..664ec6461c --- /dev/null +++ b/ui/snippets/topBar/settings/Settings.tsx @@ -0,0 +1,36 @@ +import { Box, IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import SettingsColorTheme from './SettingsColorTheme'; +import SettingsIdentIcon from './SettingsIdentIcon'; + +const Settings = () => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + return ( + + + } + p="1px" + boxSize={ 5 } + onClick={ onToggle } + /> + + + + + + + + + + ); +}; + +export default React.memo(Settings); diff --git a/ui/snippets/topBar/settings/SettingsColorTheme.tsx b/ui/snippets/topBar/settings/SettingsColorTheme.tsx new file mode 100644 index 0000000000..a8ba9c580e --- /dev/null +++ b/ui/snippets/topBar/settings/SettingsColorTheme.tsx @@ -0,0 +1,85 @@ +import { Box, Flex, useColorMode } from '@chakra-ui/react'; +import React from 'react'; + +import * as cookies from 'lib/cookies'; + +import SettingsSample from './SettingsSample'; +import { COLOR_THEMES } from './utils'; + +const SettingsColorTheme = () => { + const { setColorMode } = useColorMode(); + + const [ activeHex, setActiveHex ] = React.useState(); + + const setTheme = React.useCallback((hex: string) => { + const nextTheme = COLOR_THEMES.find((theme) => theme.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 cookieColorMode; + })(); + + const colorModeThemes = COLOR_THEMES.filter(theme => theme.colorMode === nextColorMode); + const fallbackHex = colorModeThemes[colorModeThemes.length - 1].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-value'); + + if (!hex) { + return; + } + + setTheme(hex); + setActiveHex(hex); + }, [ setTheme ]); + + const activeTheme = COLOR_THEMES.find((theme) => theme.hex === activeHex); + + return ( +
+ Color theme + { activeTheme?.label } + + { COLOR_THEMES.map((theme) => ( + + )) } + +
+ ); +}; + +export default React.memo(SettingsColorTheme); diff --git a/ui/snippets/topBar/settings/SettingsIdentIcon.tsx b/ui/snippets/topBar/settings/SettingsIdentIcon.tsx new file mode 100644 index 0000000000..af8f4b3140 --- /dev/null +++ b/ui/snippets/topBar/settings/SettingsIdentIcon.tsx @@ -0,0 +1,53 @@ +import { Box, Flex } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; +import * as cookies from 'lib/cookies'; + +import SettingsSample from './SettingsSample'; +import { IDENTICONS } from './utils'; + +const SettingsIdentIcon = () => { + const [ activeId, setActiveId ] = React.useState(); + + React.useEffect(() => { + const initialId = cookies.get(cookies.NAMES.ADDRESS_IDENTICON_TYPE) || config.UI.views.address.identiconType; + setActiveId(initialId); + }, []); + + const handleSelect = React.useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + + const id = event.currentTarget.getAttribute('data-value'); + + if (!id) { + return; + } + + cookies.set(cookies.NAMES.ADDRESS_IDENTICON_TYPE, id); + window.location.reload(); + }, []); + + const activeIdenticon = IDENTICONS.find((identicon) => identicon.id === activeId); + + return ( +
+ Address identicon + { activeIdenticon?.label } + + { IDENTICONS.map((identicon) => ( + + )) } + +
+ ); +}; + +export default React.memo(SettingsIdentIcon); diff --git a/ui/snippets/topBar/settings/SettingsSample.tsx b/ui/snippets/topBar/settings/SettingsSample.tsx new file mode 100644 index 0000000000..b6986b1862 --- /dev/null +++ b/ui/snippets/topBar/settings/SettingsSample.tsx @@ -0,0 +1,63 @@ +import { + Box, + Tooltip, + useColorModeValue, + useToken, +} from '@chakra-ui/react'; +import React from 'react'; + +interface Props { + onClick?: (event: React.MouseEvent) => void; + isActive: boolean; + bg: string; + value: string; + label: string; +} + +const TOOLTIP_OFFSET: [ number, number ] = [ 0, 10 ]; + +const SettingsSample = ({ label, value, bg, onClick, isActive }: Props) => { + const bgColor = useColorModeValue('white', 'gray.900'); + const activeBgColor = useColorModeValue('blue.50', 'whiteAlpha.100'); + + const activeBorderColor = useToken('colors', useColorModeValue('blackAlpha.800', 'gray.50')); + const hoverBorderColor = useToken('colors', 'link_hovered'); + + return ( + + + + + + ); +}; + +export default React.memo(SettingsSample); diff --git a/ui/snippets/topBar/settings/utils.ts b/ui/snippets/topBar/settings/utils.ts new file mode 100644 index 0000000000..5346846a33 --- /dev/null +++ b/ui/snippets/topBar/settings/utils.ts @@ -0,0 +1,53 @@ +import type { IdenticonType } from 'types/views/address'; + +export const COLOR_THEMES = [ + { + label: 'Light', + colorMode: 'light', + hex: '#FFFFFF', + sampleBg: 'linear-gradient(154deg, #EFEFEF 50%, rgba(255, 255, 255, 0.00) 330.86%)', + }, + { + label: 'Dim', + colorMode: 'dark', + hex: '#232B37', + sampleBg: 'linear-gradient(152deg, #232B37 50%, rgba(255, 255, 255, 0.00) 290.71%)', + }, + { + label: 'Midnight', + colorMode: 'dark', + hex: '#1B2E48', + sampleBg: 'linear-gradient(148deg, #1B3F71 50%, rgba(255, 255, 255, 0.00) 312.35%)', + }, + { + label: 'Dark', + colorMode: 'dark', + hex: '#101112', + sampleBg: 'linear-gradient(161deg, #000 9.37%, #383838 92.52%)', + }, +]; + +export type ColorTheme = typeof COLOR_THEMES[number]; + +export const IDENTICONS: Array<{ label: string; id: IdenticonType; sampleBg: string }> = [ + { + label: 'GitHub', + id: 'github', + sampleBg: 'url("/static/identicon_logos/github.png") center / contain no-repeat', + }, + { + label: 'Metamask jazzicon', + id: 'jazzicon', + sampleBg: 'url("/static/identicon_logos/jazzicon.png") center / contain no-repeat', + }, + { + label: 'Ethereum blockies', + id: 'blockie', + sampleBg: 'url("/static/identicon_logos/blockies.png") center / contain no-repeat', + }, + { + label: 'Gradient avatar', + id: 'gradient_avatar', + sampleBg: 'url("/static/identicon_logos/gradient_avatar.png") center / contain no-repeat', + }, +]; diff --git a/ui/snippets/topBar/utils.ts b/ui/snippets/topBar/utils.ts deleted file mode 100644 index 0644fcc9b2..0000000000 --- a/ui/snippets/topBar/utils.ts +++ /dev/null @@ -1,33 +0,0 @@ -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/walletMenu/useWallet.tsx b/ui/snippets/walletMenu/useWallet.tsx index cccdaa166b..370c4f4d7e 100644 --- a/ui/snippets/walletMenu/useWallet.tsx +++ b/ui/snippets/walletMenu/useWallet.tsx @@ -1,6 +1,6 @@ import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react'; import React from 'react'; -import { useAccount, useDisconnect } from 'wagmi'; +import { useAccount, useDisconnect, useAccountEffect } from 'wagmi'; import * as mixpanel from 'lib/mixpanel/index'; @@ -38,7 +38,9 @@ export default function useWallet({ source }: Params) { disconnect(); }, [ disconnect ]); - const { address, isDisconnected } = useAccount({ onConnect: handleAccountConnected }); + useAccountEffect({ onConnect: handleAccountConnected }); + + const { address, isDisconnected } = useAccount(); const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined; diff --git a/ui/token/TokenDetails.tsx b/ui/token/TokenDetails.tsx index 2a3e41e23d..d6a9c7b276 100644 --- a/ui/token/TokenDetails.tsx +++ b/ui/token/TokenDetails.tsx @@ -77,7 +77,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { let totalSupplyValue; - if (type === 'ERC-20') { + if (decimals) { const totalValue = totalSupply ? getCurrencyValue({ value: totalSupply, accuracy: 3, accuracyUsd: 2, exchangeRate, decimals }) : undefined; totalSupplyValue = totalValue?.valueStr; } else { diff --git a/ui/token/TokenHolders/TokenHoldersListItem.tsx b/ui/token/TokenHolders/TokenHoldersListItem.tsx index cfe3b31a76..65dc4b2f3a 100644 --- a/ui/token/TokenHolders/TokenHoldersListItem.tsx +++ b/ui/token/TokenHolders/TokenHoldersListItem.tsx @@ -29,7 +29,7 @@ const TokenHoldersListItem = ({ holder, token, isLoading }: Props) => { /> - { token.type === 'ERC-1155' && 'token_id' in holder && ( + { (token.type === 'ERC-1155' || token.type === 'ERC-404') && 'token_id' in holder && ( <> ID# @@ -47,7 +47,7 @@ const TokenHoldersListItem = ({ holder, token, isLoading }: Props) => { - { token.total_supply && ( + { token.total_supply && token.type !== 'ERC-404' && ( <> Percentage diff --git a/ui/token/TokenHolders/TokenHoldersTable.tsx b/ui/token/TokenHolders/TokenHoldersTable.tsx index e44e981bb6..22c229cd96 100644 --- a/ui/token/TokenHolders/TokenHoldersTable.tsx +++ b/ui/token/TokenHolders/TokenHoldersTable.tsx @@ -19,9 +19,9 @@ const TokenHoldersTable = ({ data, token, top, isLoading }: Props) => {
HolderID#ID#QuantityPercentagePercentage
{ 'token_id' in holder && holder.token_id } @@ -38,7 +38,7 @@ const TokenTransferTableItem = ({ holder, token, isLoading }: Props) => { { quantity } > = [ { field: 'github', icon: 'social/github_filled', title: 'Github' }, - { field: 'twitter', icon: 'social/twitter_filled', title: 'Twitter' }, + { field: 'twitter', icon: 'social/twitter_filled', title: 'X (ex-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' }, diff --git a/ui/token/TokenProjectInfo/TriggerButton.tsx b/ui/token/TokenProjectInfo/TriggerButton.tsx index 5398df16e1..9a96e937d5 100644 --- a/ui/token/TokenProjectInfo/TriggerButton.tsx +++ b/ui/token/TokenProjectInfo/TriggerButton.tsx @@ -17,10 +17,12 @@ const TriggerButton = ({ onClick }: Props, ref: React.ForwardedRef - + Info ); diff --git a/ui/token/TokenTransfer/TokenTransferListItem.tsx b/ui/token/TokenTransfer/TokenTransferListItem.tsx index 686c373bc9..f4c582e039 100644 --- a/ui/token/TokenTransfer/TokenTransferListItem.tsx +++ b/ui/token/TokenTransfer/TokenTransferListItem.tsx @@ -5,6 +5,7 @@ import type { TokenTransfer } from 'types/api/tokenTransfer'; import getCurrencyValue from 'lib/getCurrencyValue'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; +import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import AddressFromTo from 'ui/shared/address/AddressFromTo'; import Tag from 'ui/shared/chakra/Tag'; import NftEntity from 'ui/shared/entities/nft/NftEntity'; @@ -26,7 +27,7 @@ const TokenTransferListItem = ({ isLoading, }: Props) => { const timeAgo = useTimeAgoIncrement(timestamp, true); - const { usd, valueStr } = 'value' in total ? getCurrencyValue({ + const { usd, valueStr } = 'value' in total && total.value !== null ? getCurrencyValue({ value: total.value, exchangeRate: token.exchange_rate, accuracy: 8, @@ -87,7 +88,7 @@ const TokenTransferListItem = ({ ) } ) } - { 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && total.token_id !== null && ( + { 'token_id' in total && (NFT_TOKEN_TYPE_IDS.includes(token.type)) && total.token_id !== null && ( Txn hash MethodFrom/ToToken IDFrom/ToToken ID + { (tokenType === 'ERC-20' || tokenType === 'ERC-1155' || tokenType === 'ERC-404') && ( + { 'token_id' in total && total.token_id !== null ? ( ) } - { (token.type === 'ERC-20' || token.type === 'ERC-1155') && ( + { (token.type === 'ERC-20' || token.type === 'ERC-1155' || token.type === 'ERC-404') && ( { valueStr && ( diff --git a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png index 8eef0d0de8..3addedffff 100644 Binary files a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ diff --git a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_base-view-mobile-dark-mode-1.png index d1737b30ec..476c4bccfb 100644 Binary files a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_base-view-mobile-dark-mode-1.png differ diff --git a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_status-IN-PROCESS-1.png b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_status-IN-PROCESS-1.png index a05db20f6f..de6e7ad7b7 100644 Binary files a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_status-IN-PROCESS-1.png and b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_default_status-IN-PROCESS-1.png differ diff --git a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png index b64f67d6ac..05cc4a0efb 100644 Binary files a/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/tokenInfo/__screenshots__/TokenInfoForm.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ diff --git a/ui/tokenInfo/fields/TokenInfoFieldSocialLink.tsx b/ui/tokenInfo/fields/TokenInfoFieldSocialLink.tsx index c0521edf66..bd404b4678 100644 --- a/ui/tokenInfo/fields/TokenInfoFieldSocialLink.tsx +++ b/ui/tokenInfo/fields/TokenInfoFieldSocialLink.tsx @@ -21,7 +21,7 @@ const SETTINGS: Record = { 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' }, + twitter: { label: 'X (ex-Twitter)', icon: 'social/twitter_filled', color: 'inherit' }, 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' }, diff --git a/ui/tx/FtTokenTransferSnippet.tsx b/ui/tx/FtTokenTransferSnippet.tsx new file mode 100644 index 0000000000..282eb406aa --- /dev/null +++ b/ui/tx/FtTokenTransferSnippet.tsx @@ -0,0 +1,37 @@ +import { chakra } from '@chakra-ui/react'; +import React from 'react'; + +import type { TokenInfo } from 'types/api/token'; + +import getCurrencyValue from 'lib/getCurrencyValue'; +import TokenEntity from 'ui/shared/entities/token/TokenEntity'; + +interface Props { + token: TokenInfo; + value: string; + decimals: string | null; +} +const FtTokenTransferSnippet = ({ token, value, decimals }: Props) => { + const { valueStr, usd } = getCurrencyValue({ + value: value, + exchangeRate: token.exchange_rate, + accuracyUsd: 2, + decimals: decimals, + }); + + return ( + <> + for + { valueStr } + + { usd && (${ usd }) } + + ); +}; + +export default React.memo(FtTokenTransferSnippet); diff --git a/ui/tx/NftTokenTransferSnippet.tsx b/ui/tx/NftTokenTransferSnippet.tsx index ece1fa15ef..be304f70db 100644 --- a/ui/tx/NftTokenTransferSnippet.tsx +++ b/ui/tx/NftTokenTransferSnippet.tsx @@ -15,6 +15,29 @@ interface Props { const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => { const num = value === '1' ? '' : value; + const tokenIdContent = (() => { + if (tokenId === null) { + // ERC-404 may not have an ID + if (token.type === 'ERC-404') { + return null; + } + return N/A ; + } + + return ( + + ); + + })(); + return ( <> { num ? ( @@ -26,18 +49,7 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => { ) : ( for token ID ) } - { tokenId !== null ? ( - - ) : N/A - } + { tokenIdContent } of (1); + + const ViewData = useMemo(() => (queryData ? generateFlowViewData(queryData) : []), [ queryData ]); + const chunkedViewData = _.chunk(ViewData, 50); + + const paginationProps: PaginationParams = useMemo(() => ({ + onNextPageClick: () => setPage(page + 1), + onPrevPageClick: () => setPage(page - 1), + resetPage: () => setPage(1), + canGoBackwards: page > 1, + isLoading: isPlaceholderData, + page: page, + hasNextPage: Boolean(chunkedViewData[page]), + hasPages: Boolean(chunkedViewData[1]), + isVisible: Boolean(chunkedViewData[1]), + }), [ chunkedViewData, page, isPlaceholderData ]); + + const data = chunkedViewData [page - 1]; + + const actionBar = ( + + + + + Wallet + + + + + + + + ); + + const content = ( + <> + + { data?.map((item, i) => ( + + )) } + + + + + + + + + + + + { data?.map((item, i) => ( + + )) } + +
+ Actions + + From/To +
+
+ + ); + + return ( + + ); +} diff --git a/ui/tx/TxSubHeading.tsx b/ui/tx/TxSubHeading.tsx index 49976765ef..7ee1336512 100644 --- a/ui/tx/TxSubHeading.tsx +++ b/ui/tx/TxSubHeading.tsx @@ -3,6 +3,7 @@ import React from 'react'; import config from 'configs/app'; import useApiQuery from 'lib/api/useApiQuery'; +import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate'; import { TX_INTERPRETATION } from 'stubs/txInterpretation'; import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu'; import { TX_ACTIONS_BLOCK_ID } from 'ui/shared/DetailsActionsWrapper'; @@ -10,6 +11,7 @@ import TxEntity from 'ui/shared/entities/tx/TxEntity'; import NetworkExplorers from 'ui/shared/NetworkExplorers'; import TxInterpretation from 'ui/shared/tx/interpretation/TxInterpretation'; +import { createNovesSummaryObject } from './assetFlows/utils/createNovesSummaryObject'; import type { TxQuery } from './useTxQuery'; type Props = { @@ -18,25 +20,50 @@ type Props = { txQuery: TxQuery; } +const feature = config.features.txInterpretation; + const TxSubHeading = ({ hash, hasTag, txQuery }: Props) => { - const hasInterpretationFeature = config.features.txInterpretation.isEnabled; + const hasInterpretationFeature = feature.isEnabled; + const isNovesInterpretation = hasInterpretationFeature && feature.provider === 'noves'; const txInterpretationQuery = useApiQuery('tx_interpretation', { pathParams: { hash }, queryOptions: { - enabled: Boolean(hash) && hasInterpretationFeature, + enabled: Boolean(hash) && (hasInterpretationFeature && !isNovesInterpretation), placeholderData: TX_INTERPRETATION, }, }); + const novesInterpretationQuery = useApiQuery('noves_transaction', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash) && isNovesInterpretation, + placeholderData: NOVES_TRANSLATE, + }, + }); + const content = (() => { - const hasInterpretation = hasInterpretationFeature && + const hasNovesInterpretation = isNovesInterpretation && + (novesInterpretationQuery.isPlaceholderData || Boolean(novesInterpretationQuery.data?.classificationData.description)); + + const hasInternalInterpretation = (hasInterpretationFeature && !isNovesInterpretation) && (txInterpretationQuery.isPlaceholderData || Boolean(txInterpretationQuery.data?.data.summaries.length)); const hasViewAllInterpretationsLink = !txInterpretationQuery.isPlaceholderData && txInterpretationQuery.data?.data.summaries && txInterpretationQuery.data?.data.summaries.length > 1; - if (hasInterpretation) { + if (hasNovesInterpretation && novesInterpretationQuery.data) { + const novesSummary = createNovesSummaryObject(novesInterpretationQuery.data); + + return ( + + ); + } else if (hasInternalInterpretation) { return ( { + + return ( + + + + + + + + Action + + + + + + + + + + + + ); +}; + +export default React.memo(TxAssetFlowsListItem); diff --git a/ui/tx/assetFlows/TxAssetFlowsTableItem.tsx b/ui/tx/assetFlows/TxAssetFlowsTableItem.tsx new file mode 100644 index 0000000000..0e2056b038 --- /dev/null +++ b/ui/tx/assetFlows/TxAssetFlowsTableItem.tsx @@ -0,0 +1,28 @@ +import { Td, Tr } from '@chakra-ui/react'; +import React from 'react'; + +import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; + +import NovesActionSnippet from './components/NovesActionSnippet'; +import type { NovesFlowViewItem } from './utils/generateFlowViewData'; + +type Props = { + isPlaceholderData: boolean; + item: NovesFlowViewItem; +}; + +const TxAssetFlowsTableItem = (props: Props) => { + + return ( +
+ + + +
+ + + + + + + + + + + + { items.map((item, index) => ( + + )) } + +
Batch #StatusAgeTxn countCommit txProve tx
+ ); +}; + +export default ZkSyncTxnBatchesTable; diff --git a/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTableItem.tsx b/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTableItem.tsx new file mode 100644 index 0000000000..82a2f93fdf --- /dev/null +++ b/ui/txnBatches/zkSyncL2/ZkSyncTxnBatchesTableItem.tsx @@ -0,0 +1,84 @@ +import { Td, Tr, Text, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import type { ZkSyncBatchesItem } from 'types/api/zkSyncL2'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; +import dayjs from 'lib/date/dayjs'; +import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; +import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; +import LinkInternal from 'ui/shared/LinkInternal'; +import ZkSyncL2TxnBatchStatus from 'ui/shared/statusTag/ZkSyncL2TxnBatchStatus'; + +const rollupFeature = config.features.rollup; + +type Props = { item: ZkSyncBatchesItem; isLoading?: boolean }; + +const ZkSyncTxnBatchesTableItem = ({ item, isLoading }: Props) => { + const timeAgo = item.timestamp ? dayjs(item.timestamp).fromNow() : 'Undefined'; + + if (!rollupFeature.isEnabled || rollupFeature.type !== 'zkSync') { + return null; + } + + return ( + + + + + + + + + + { timeAgo } + + + + + + { item.tx_count } + + + + + { item.commit_transaction_hash ? ( + + ) : Pending } + + + { item.prove_transaction_hash ? ( + + ) : Pending } + + + ); +}; + +export default ZkSyncTxnBatchesTableItem; diff --git a/ui/txs/TxTranslationType.tsx b/ui/txs/TxTranslationType.tsx new file mode 100644 index 0000000000..4086efb80f --- /dev/null +++ b/ui/txs/TxTranslationType.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import type { TransactionType } from 'types/api/transaction'; + +import Tag from 'ui/shared/chakra/Tag'; + +import { camelCaseToSentence } from './noves/utils'; +import TxType from './TxType'; + +export interface Props { + types: Array; + isLoading?: boolean; + translatationType: string | undefined; +} + +const TxTranslationType = ({ types, isLoading, translatationType }: Props) => { + + const filteredTypes = [ 'unclassified' ]; + + if (!translatationType || filteredTypes.includes(translatationType)) { + return ; + } + + return ( + + { camelCaseToSentence(translatationType) } + + ); + +}; + +export default TxTranslationType; diff --git a/ui/txs/TxsContent.tsx b/ui/txs/TxsContent.tsx index a7011e033c..44f687afc5 100644 --- a/ui/txs/TxsContent.tsx +++ b/ui/txs/TxsContent.tsx @@ -10,6 +10,7 @@ import DataListDisplay from 'ui/shared/DataListDisplay'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import getNextSortValue from 'ui/shared/sort/getNextSortValue'; +import useDescribeTxs from './noves/useDescribeTxs'; import TxsHeaderMobile from './TxsHeaderMobile'; import TxsList from './TxsList'; import TxsTable from './TxsTable'; @@ -62,7 +63,9 @@ const TxsContent = ({ setSorting(value); }, [ sort, setSorting ]); - const content = items ? ( + const itemsWithTranslation = useDescribeTxs(items, currentAddress, query.isPlaceholderData); + + const content = itemsWithTranslation ? ( <> - + { tx.translation ? + : + + } @@ -51,6 +56,7 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI hash={ tx.hash } truncation="constant_long" fontWeight="700" + iconName={ tx.tx_types.includes('blob_transaction') ? 'blob' : undefined } /> { tx.timestamp && ( diff --git a/ui/txs/TxsTableItem.tsx b/ui/txs/TxsTableItem.tsx index b1a4121cf5..216003bfeb 100644 --- a/ui/txs/TxsTableItem.tsx +++ b/ui/txs/TxsTableItem.tsx @@ -21,6 +21,7 @@ import TxFeeStability from 'ui/shared/tx/TxFeeStability'; import TxWatchListTags from 'ui/shared/tx/TxWatchListTags'; import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo'; +import TxTranslationType from './TxTranslationType'; import TxType from './TxType'; type Props = { @@ -62,7 +63,10 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, - + { tx.translation ? + : + + } diff --git a/ui/txs/noves/useDescribeTxs.tsx b/ui/txs/noves/useDescribeTxs.tsx new file mode 100644 index 0000000000..d9081492cd --- /dev/null +++ b/ui/txs/noves/useDescribeTxs.tsx @@ -0,0 +1,89 @@ +import { useQuery } from '@tanstack/react-query'; +import _ from 'lodash'; +import React from 'react'; + +import type { NovesDescribeTxsResponse } from 'types/api/noves'; +import type { Transaction } from 'types/api/transaction'; + +import config from 'configs/app'; +import useApiFetch from 'lib/api/useApiFetch'; + +const feature = config.features.txInterpretation; + +const translateEnabled = feature.isEnabled && feature.provider === 'noves'; + +export default function useDescribeTxs(items: Array | undefined, viewAsAccountAddress: string | undefined, isPlaceholderData: boolean) { + const apiFetch = useApiFetch(); + + const txsHash = _.uniq(items?.map(i => i.hash)); + const txChunks = _.chunk(txsHash, 10); + + const queryKey = { + viewAsAccountAddress, + firstHash: txsHash[0] || '', + lastHash: txsHash[txsHash.length - 1] || '', + }; + + const describeQuery = useQuery({ + queryKey: [ 'noves_describe_txs', queryKey ], + queryFn: async() => { + const queries = txChunks.map((hashes) => { + if (hashes.length === 0) { + return Promise.resolve([]); + } + + return apiFetch('noves_describe_txs', { + queryParams: { + viewAsAccountAddress, + hashes, + }, + }) as Promise; + }); + + return Promise.all(queries); + }, + select: (data) => { + return data.flat(); + }, + enabled: translateEnabled && !isPlaceholderData, + }); + + const itemsWithTranslation = React.useMemo(() => items?.map(tx => { + const queryData = describeQuery.data; + const isLoading = describeQuery.isLoading; + + if (isLoading) { + return { + ...tx, + translation: { + isLoading, + }, + }; + } + + if (!queryData || !translateEnabled) { + return tx; + } + + const query = queryData.find(data => data.txHash.toLowerCase() === tx.hash.toLowerCase()); + + if (query) { + return { + ...tx, + translation: { + data: query, + isLoading: false, + }, + }; + } + + return tx; + }), [ items, describeQuery ]); + + if (!translateEnabled || isPlaceholderData) { + return items; + } + + // return same "items" array of Transaction with a new "translation" field. + return itemsWithTranslation; +} diff --git a/ui/txs/noves/utils.ts b/ui/txs/noves/utils.ts new file mode 100644 index 0000000000..c5a986b66a --- /dev/null +++ b/ui/txs/noves/utils.ts @@ -0,0 +1,11 @@ +export function camelCaseToSentence(camelCaseString: string | undefined) { + if (!camelCaseString) { + return ''; + } + + let sentence = camelCaseString.replace(/([a-z])([A-Z])/g, '$1 $2'); + sentence = sentence.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2'); + sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1); + + return sentence; +} diff --git a/ui/txs/sortTxs.test.ts b/ui/txs/sortTxs.test.ts new file mode 100644 index 0000000000..d4d33e5b92 --- /dev/null +++ b/ui/txs/sortTxs.test.ts @@ -0,0 +1,79 @@ +import type { Transaction } from 'types/api/transaction'; + +import sortTxs, { sortTxsFromSocket } from './sortTxs'; + +describe('sortTxs', () => { + it('should sort transactions by value in descending order', () => { + const txs = [ + { value: '42' }, + { value: '11' }, + { value: '24' }, + ] as Array; + const result = txs.sort(sortTxs('value-desc')); + expect(result).toEqual([ + { value: '42' }, + { value: '24' }, + { value: '11' }, + ]); + }); + + it('should sort transactions by value in ascending order', () => { + const txs = [ + { value: '42' }, + { value: '11' }, + { value: '24' }, + ] as Array; + const result = txs.sort(sortTxs('value-asc')); + expect(result).toEqual([ + { value: '11' }, + { value: '24' }, + { value: '42' }, + ]); + }); + + it('should sort transactions by fee in descending order', () => { + const txs = [ + { fee: { value: '42' } }, + { fee: { value: '11' } }, + { fee: { value: '24' } }, + ] as Array; + const result = txs.sort(sortTxs('fee-desc')); + expect(result).toEqual([ + { fee: { value: '42' } }, + { fee: { value: '24' } }, + { fee: { value: '11' } }, + ]); + }); + + it('should sort transactions by fee in ascending order', () => { + const txs = [ + { fee: { value: '42' } }, + { fee: { value: '11' } }, + { fee: { value: '24' } }, + ] as Array; + const result = txs.sort(sortTxs('fee-asc')); + expect(result).toEqual([ + { fee: { value: '11' } }, + { fee: { value: '24' } }, + { fee: { value: '42' } }, + ]); + }); +}); + +describe('sortTxsFromSocket', () => { + it('should sort transaction by age in ascending order if sorting is not provided', () => { + const txs = [ + { timestamp: '2022-11-01T12:33:00Z' }, + { timestamp: '2022-11-01T12:00:00Z' }, + { timestamp: null }, + { timestamp: '2022-11-03T03:03:00Z' }, + ] as Array; + const result = txs.sort(sortTxsFromSocket(undefined)); + expect(result).toEqual([ + { timestamp: null }, + { timestamp: '2022-11-03T03:03:00Z' }, + { timestamp: '2022-11-01T12:33:00Z' }, + { timestamp: '2022-11-01T12:00:00Z' }, + ]); + }); +}); diff --git a/ui/txs/sortTxs.ts b/ui/txs/sortTxs.ts new file mode 100644 index 0000000000..3993612e4c --- /dev/null +++ b/ui/txs/sortTxs.ts @@ -0,0 +1,38 @@ +import type { Transaction, TransactionsSortingValue } from 'types/api/transaction'; + +import compareBns from 'lib/bigint/compareBns'; + +export default function sortTxs(sorting: TransactionsSortingValue | undefined) { + return function sortingFn(tx1: Transaction, tx2: Transaction) { + switch (sorting) { + case 'value-desc': + return compareBns(tx2.value, tx1.value); + case 'value-asc': + return compareBns(tx1.value, tx2.value); + case 'fee-desc': + return compareBns(tx2.fee.value || 0, tx1.fee.value || 0); + case 'fee-asc': + return compareBns(tx1.fee.value || 0, tx2.fee.value || 0); + default: + return 0; + } + }; +} + +export function sortTxsFromSocket(sorting: TransactionsSortingValue | undefined) { + if (sorting) { + return sortTxs(sorting); + } + + return function sortingFn(tx1: Transaction, tx2: Transaction) { + if (!tx1.timestamp) { + return -1; + } + + if (!tx2.timestamp) { + return 1; + } + + return tx2.timestamp.localeCompare(tx1.timestamp); + }; +} diff --git a/ui/txs/useTxsSort.tsx b/ui/txs/useTxsSort.tsx index bee6eefddd..c2ec9d8377 100644 --- a/ui/txs/useTxsSort.tsx +++ b/ui/txs/useTxsSort.tsx @@ -1,13 +1,14 @@ import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; -import type { Transaction, TransactionsSortingValue, TxsResponse } from 'types/api/transaction'; +import type { 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 type { Option } from 'ui/shared/sort/Sort'; +import sortTxs from './sortTxs'; + export const SORT_OPTIONS: Array> = [ { title: 'Default', id: undefined }, { title: 'Value ascending', id: 'value-asc' }, @@ -23,21 +24,6 @@ type HookResult = UseQueryResult> & { 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 { diff --git a/ui/watchlist/AddressModal/AddressForm.tsx b/ui/watchlist/AddressModal/AddressForm.tsx index 613d4029ae..f5db08aef4 100644 --- a/ui/watchlist/AddressModal/AddressForm.tsx +++ b/ui/watchlist/AddressModal/AddressForm.tsx @@ -22,7 +22,7 @@ import TagInput from 'ui/shared/TagInput'; import AddressFormNotifications from './AddressFormNotifications'; // does it depend on the network? -const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const; +const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721', 'ERC-404' ] as const; const TAG_MAX_LENGTH = 35; @@ -50,6 +50,10 @@ type Inputs = { outcoming: boolean; incoming: boolean; }; + 'ERC-404': { + outcoming: boolean; + incoming: boolean; + }; }; } @@ -59,7 +63,9 @@ type Checkboxes = 'notification' | 'notification_settings.ERC-20.outcoming' | 'notification_settings.ERC-20.incoming' | 'notification_settings.ERC-721.outcoming' | -'notification_settings.ERC-721.incoming'; +'notification_settings.ERC-721.incoming' | +'notification_settings.ERC-404.outcoming' | +'notification_settings.ERC-404.incoming'; const AddressForm: React.FC = ({ data, onSuccess, setAlertVisible, isAdd }) => { const [ pending, setPending ] = useState(false); diff --git a/ui/watchlist/AddressModal/AddressFormNotifications.tsx b/ui/watchlist/AddressModal/AddressFormNotifications.tsx index 6b3b94d329..23ca3d9ff9 100644 --- a/ui/watchlist/AddressModal/AddressFormNotifications.tsx +++ b/ui/watchlist/AddressModal/AddressFormNotifications.tsx @@ -7,8 +7,8 @@ import config from 'configs/app'; import CheckboxInput from 'ui/shared/CheckboxInput'; // does it depend on the network? -const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const; -const NOTIFICATIONS_NAMES = [ config.chain.currency.symbol, 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ]; +const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721', 'ERC-404' ] as const; +const NOTIFICATIONS_NAMES = [ config.chain.currency.symbol, 'ERC-20', 'ERC-721, ERC-1155 (NFT)', 'ERC-404' ]; type Props = { control: Control; diff --git a/ui/withdrawals/optimisticL2/OptimisticL2WithdrawalsTable.tsx b/ui/withdrawals/optimisticL2/OptimisticL2WithdrawalsTable.tsx index b46eb35c69..0198858bb1 100644 --- a/ui/withdrawals/optimisticL2/OptimisticL2WithdrawalsTable.tsx +++ b/ui/withdrawals/optimisticL2/OptimisticL2WithdrawalsTable.tsx @@ -29,7 +29,11 @@ const OptimisticL2WithdrawalsTable = ({ items, top, isLoading }: Props) => { { items.map((item, index) => ( - + )) } diff --git a/yarn.lock b/yarn.lock index 87c181344a..353e3c618c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1216,12 +1216,12 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" - integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== +"@babel/runtime@^7.20.6", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== dependencies: - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" "@babel/runtime@^7.23.5": version "7.23.6" @@ -1230,6 +1230,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.5.5": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.8.4": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" @@ -2149,28 +2156,20 @@ 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" - integrity sha512-vX+epj/Ttjo7XRwlr3TFUUfW5GTRMvORpERPwiu7z2jl3DSVL4rXLmHt5y6LDPlUVreas2gumdcFbu0fLRG9Jg== +"@coinbase/wallet-sdk@3.9.1": + version "3.9.1" + resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.9.1.tgz#503a14671bb392d653623ef2340934e213ac971f" + integrity sha512-cGUE8wm1/cMI8irRMVOqbFWYcnNugqCtuy2lnnHfgloBg+GRLs9RsrkOUDMdv/StfUeeKhCDyYudsXXvcL1xIA== dependencies: - "@metamask/safe-event-emitter" "2.0.0" - "@solana/web3.js" "^1.70.1" - bind-decorator "^1.0.11" - bn.js "^5.1.1" + bn.js "^5.2.1" buffer "^6.0.3" - clsx "^1.1.0" - eth-block-tracker "6.1.0" - eth-json-rpc-filters "5.1.0" - eth-rpc-errors "4.0.2" - json-rpc-engine "6.1.0" - keccak "^3.0.1" - preact "^10.5.9" - qs "^6.10.3" - rxjs "^6.6.3" + clsx "^1.2.1" + eth-block-tracker "^7.1.0" + eth-json-rpc-filters "^6.0.0" + eventemitter3 "^5.0.1" + keccak "^3.0.3" + preact "^10.16.0" sha.js "^2.4.11" - stream-browserify "^3.0.0" - util "^0.12.4" "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -2202,6 +2201,23 @@ source-map "^0.5.7" stylis "4.1.3" +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + "@emotion/cache@^11.10.5", "@emotion/cache@^11.4.0": version "11.10.5" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12" @@ -2213,11 +2229,27 @@ "@emotion/weak-memoize" "^0.3.0" stylis "4.1.3" +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + "@emotion/hash@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + "@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -2232,6 +2264,13 @@ dependencies: "@emotion/memoize" "^0.8.0" +"@emotion/is-prop-valid@^1.2.1": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" @@ -2242,6 +2281,11 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + "@emotion/react@^11.10.4", "@emotion/react@^11.8.1": version "11.10.5" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.5.tgz#95fff612a5de1efa9c0d535384d3cfa115fe175d" @@ -2256,6 +2300,20 @@ "@emotion/weak-memoize" "^0.3.0" hoist-non-react-statics "^3.3.1" +"@emotion/react@^11.10.6": + version "11.11.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" + integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + "@emotion/serialize@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0" @@ -2267,11 +2325,27 @@ "@emotion/utils" "^1.2.0" csstype "^3.0.2" +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.3.tgz#84b77bfcfe3b7bb47d326602f640ccfcacd5ffb0" + integrity sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + "@emotion/sheet@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c" integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA== +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + "@emotion/styled@^11.10.4": version "11.10.5" resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.5.tgz#1fe7bf941b0909802cb826457e362444e7e96a79" @@ -2284,26 +2358,58 @@ "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" "@emotion/utils" "^1.2.0" +"@emotion/styled@^11.10.6": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346" + integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/is-prop-valid" "^1.2.1" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/unitless@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + "@emotion/use-insertion-effect-with-fallbacks@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A== +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + "@emotion/utils@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + "@emotion/weak-memoize@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" @@ -2454,7 +2560,7 @@ resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== -"@ethereumjs/tx@^4.1.2": +"@ethereumjs/tx@^4.1.2", "@ethereumjs/tx@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-4.2.0.tgz#5988ae15daf5a3b3c815493bc6b495e76009e853" integrity sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw== @@ -2959,6 +3065,24 @@ dependencies: "@lit-labs/ssr-dom-shim" "^1.1.2" +"@metamask/eth-json-rpc-provider@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz#3fd5316c767847f4ca107518b611b15396a5a32c" + integrity sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA== + dependencies: + "@metamask/json-rpc-engine" "^7.0.0" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^5.0.1" + +"@metamask/json-rpc-engine@^7.0.0": + version "7.3.3" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz#f2b30a2164558014bfcca45db10f5af291d989af" + integrity sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg== + dependencies: + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + "@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" @@ -2968,6 +3092,21 @@ once "^1.4.0" readable-stream "^2.3.3" +"@metamask/onboarding@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-1.0.1.tgz#14a36e1e175e2f69f09598e2008ab6dc1b3297e6" + integrity sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ== + dependencies: + bowser "^2.9.0" + +"@metamask/post-message-stream@^6.1.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-6.2.0.tgz#3db0a50adc2b2206d1bb95739e7fff49e36e0324" + integrity sha512-WunZ0bruClF862mvbKQGETn5SM0XKGmocPMQR1Ew6sYix9/FDzeoZnoI8RkXk01E+70FCdxhTE/r8kk5SFOuTw== + dependencies: + "@metamask/utils" "^5.0.0" + readable-stream "2.3.3" + "@metamask/post-message-stream@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-7.0.0.tgz#b7698909ec54923b6a6990b8d46cc15c2810d840" @@ -2994,22 +3133,83 @@ pump "^3.0.0" webextension-polyfill-ts "^0.25.0" -"@metamask/safe-event-emitter@2.0.0", "@metamask/safe-event-emitter@^2.0.0": +"@metamask/rpc-errors@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-6.2.1.tgz#f5daf429ededa7cb83069dc621bd5738fe2a1d80" + integrity sha512-VTgWkjWLzb0nupkFl1duQi9Mk8TGT9rsdnQg6DeRrYEFxtFOh0IF8nAwxM/4GWqDl6uIB06lqUBgUrAVWl62Bw== + dependencies: + "@metamask/utils" "^8.3.0" + fast-safe-stringify "^2.0.6" + +"@metamask/safe-event-emitter@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c" integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q== -"@metamask/utils@^3.0.1": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-3.6.0.tgz#b218b969a05ca7a8093b5d1670f6625061de707d" - integrity sha512-9cIRrfkWvHblSiNDVXsjivqa9Ak0RYo/1H6tqTqTbAx+oBK2Sva0lWDHxGchOqA7bySGUJKAWSNJvH6gdHZ0gQ== +"@metamask/safe-event-emitter@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.0.0.tgz#8c2b9073fe0722d48693143b0dc8448840daa3bd" + integrity sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ== + +"@metamask/sdk-communication-layer@0.14.3": + version "0.14.3" + resolved "https://registry.yarnpkg.com/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.14.3.tgz#0e7ec8e472641273da5802f3b357687ce12369c3" + integrity sha512-yjSbj8y7fFbQXv2HBzUX6D9C8BimkCYP6BDV7hdw53W8b/GlYCtXVxUFajQ9tuO1xPTRjR/xt/dkdr2aCi6WGw== dependencies: - "@types/debug" "^4.1.7" - debug "^4.3.4" - semver "^7.3.8" - superstruct "^1.0.3" + bufferutil "^4.0.8" + cross-fetch "^3.1.5" + date-fns "^2.29.3" + eciesjs "^0.3.16" + eventemitter2 "^6.4.5" + socket.io-client "^4.5.1" + utf-8-validate "^6.0.3" + uuid "^8.3.2" -"@metamask/utils@^5.0.0": +"@metamask/sdk-install-modal-web@0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.14.1.tgz#c8e64b4f7d2dac262c2ec28025c541b258478c31" + integrity sha512-emT8HKbnfVwGhPxyUfMja6DWzvtJvDEBQxqCVx93H0HsyrrOzOC43iGCAosslw6o5h7gOfRKLqWmK8V7jQAS2Q== + dependencies: + "@emotion/react" "^11.10.6" + "@emotion/styled" "^11.10.6" + i18next "22.5.1" + qr-code-styling "^1.6.0-rc.1" + react "^18.2.0" + react-dom "^18.2.0" + react-i18next "^13.2.2" + +"@metamask/sdk@0.14.3": + version "0.14.3" + resolved "https://registry.yarnpkg.com/@metamask/sdk/-/sdk-0.14.3.tgz#ec1ecf00edef981fd17e2c5cf4ec40ce0a43a55c" + integrity sha512-BYLs//nY2wioVSih78gOQI6sLIYY3vWkwVqXGYUgkBV+bi49bv+9S0m+hZ2cwiRaxfMYtKs0KvhAQ8weiYwDrg== + dependencies: + "@metamask/onboarding" "^1.0.1" + "@metamask/post-message-stream" "^6.1.0" + "@metamask/providers" "^10.2.1" + "@metamask/sdk-communication-layer" "0.14.3" + "@metamask/sdk-install-modal-web" "0.14.1" + "@react-native-async-storage/async-storage" "^1.17.11" + "@types/dom-screen-wake-lock" "^1.0.0" + bowser "^2.9.0" + cross-fetch "^4.0.0" + eciesjs "^0.3.15" + eth-rpc-errors "^4.0.3" + eventemitter2 "^6.4.7" + extension-port-stream "^2.0.1" + i18next "22.5.1" + i18next-browser-languagedetector "^7.1.0" + obj-multiplex "^1.0.0" + pump "^3.0.0" + qrcode-terminal-nooctal "^0.12.1" + react-i18next "^13.2.2" + react-native-webview "^11.26.0" + readable-stream "^2.3.7" + rollup-plugin-visualizer "^5.9.2" + socket.io-client "^4.5.1" + util "^0.12.4" + uuid "^8.3.2" + +"@metamask/utils@^5.0.0", "@metamask/utils@^5.0.1": version "5.0.2" resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-5.0.2.tgz#140ba5061d90d9dac0280c19cab101bc18c8857c" integrity sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g== @@ -3020,6 +3220,20 @@ semver "^7.3.8" superstruct "^1.0.3" +"@metamask/utils@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-8.3.0.tgz#a20de447aeb9ffb75924d822a186a597033984b6" + integrity sha512-WFVcMPEkKKRCJ8DDkZUTVbLlpwgRn98F4VM/WzN89HM8PmHMnCyk/oG0AmK/seOxtik7uC7Bbi2YBC5Z5XB2zw== + dependencies: + "@ethereumjs/tx" "^4.2.0" + "@noble/hashes" "^1.3.1" + "@scure/base" "^1.1.3" + "@types/debug" "^4.1.7" + debug "^4.3.4" + pony-cause "^2.1.10" + semver "^7.5.4" + superstruct "^1.0.3" + "@monaco-editor/loader@^1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.3.2.tgz#04effbb87052d19cd7d3c9d81c0635490f9bb6d8" @@ -3247,11 +3461,6 @@ 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" - integrity sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw== - "@noble/hashes@1.3.0", "@noble/hashes@~1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" @@ -3267,21 +3476,11 @@ 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.2": +"@noble/hashes@^1.3.1", "@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" - resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" - integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3329,6 +3528,11 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.7.0.tgz#b139c81999c23e3c8d3c0a7234480e945920fc40" integrity sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw== +"@opentelemetry/api@^1.4.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" + integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w== + "@opentelemetry/auto-instrumentations-node@^0.39.4": version "0.39.4" resolved "https://registry.yarnpkg.com/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.39.4.tgz#c772201ba224e6ebffaaf17c9f2234e6e8343328" @@ -4601,6 +4805,13 @@ prop-types "^15.7.2" tslib "^2.3.0" +"@react-native-async-storage/async-storage@^1.17.11": + version "1.22.3" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.22.3.tgz#ad490236a9eda8ac68cffc12c738f20b1815464e" + integrity sha512-Ov3wjuqxHd62tLYfgTjxj77YRYWra3A4Fi8uICIPcePgNO2WkS5B0ADXt9e/JLzSCNqVlXCq4Fir/gHmZTU9ww== + dependencies: + merge-options "^3.0.4" + "@rollup/pluginutils@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" @@ -4615,7 +4826,7 @@ 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.18.1": +"@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== @@ -4623,7 +4834,7 @@ "@safe-global/safe-apps-sdk" "^8.1.0" events "^3.3.0" -"@safe-global/safe-apps-sdk@^8.1.0": +"@safe-global/safe-apps-sdk@8.1.0", "@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== @@ -4638,16 +4849,16 @@ dependencies: cross-fetch "^3.1.5" +"@scure/base@^1.1.3", "@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/base@~1.1.0": version "1.1.1" 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" @@ -4798,34 +5009,10 @@ dependencies: react-script-hook "^1.7.2" -"@solana/buffer-layout@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" - integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== - dependencies: - buffer "~6.0.3" - -"@solana/web3.js@^1.70.1": - version "1.73.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.0.tgz#c65f9f954ac80fca6952765c931dd72e57e1b572" - integrity sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw== - dependencies: - "@babel/runtime" "^7.12.5" - "@noble/ed25519" "^1.7.0" - "@noble/hashes" "^1.1.2" - "@noble/secp256k1" "^1.6.3" - "@solana/buffer-layout" "^4.0.0" - agentkeepalive "^4.2.1" - bigint-buffer "^1.1.5" - bn.js "^5.0.0" - borsh "^0.7.0" - bs58 "^4.0.1" - buffer "6.0.1" - fast-stable-stringify "^1.0.0" - jayson "^3.4.4" - node-fetch "2" - rpc-websockets "^7.5.0" - superstruct "^0.14.2" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== "@stablelib/aead@^1.0.1": version "1.0.1" @@ -5395,35 +5582,16 @@ dependencies: "@typescript-eslint/utils" "^5.54.0" -"@tanstack/query-core@4.29.11": - version "4.29.11" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.29.11.tgz#fa338f7d6897c6be5de6d8dabd603d9b78ee48c7" - integrity sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ== - -"@tanstack/query-core@5.4.3": - version "5.4.3" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.4.3.tgz#fbdd36ccf1acf70579980f2e7cf16d2c2aa2a5e9" - integrity sha512-fnI9ORjcuLGm1sNrKatKIosRQUpuqcD4SV7RqRSVmj8JSicX2aoMyKryHEBpVQvf6N4PaBVgBxQomjsbsGPssQ== +"@tanstack/query-core@5.25.0": + version "5.25.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.25.0.tgz#e08ed0a9fad34c8005d1a282e57280031ac50cdc" + integrity sha512-vlobHP64HTuSE68lWF1mEhwSRC5Q7gaT+a/m9S+ItuN+ruSOxe1rFnR9j0ACWQ314BPhBEVKfBQ6mHL0OWfdbQ== "@tanstack/query-devtools@5.4.2": version "5.4.2" resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.4.2.tgz#1687645ba1b9ffc76c55ac7759461d331d0776bf" integrity sha512-EXdaMXi8CxZuMp97J5mq6wy1RduOfoWFv5vtA1U+hyqb8Wst6M8kkkjDSdFvGZIRpYY4K8mKLlEFHNUZDG5dtw== -"@tanstack/query-persist-client-core@4.29.11": - version "4.29.11" - resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-4.29.11.tgz#96b4b83bead480eb37e024a59fd59bfd84b0545e" - integrity sha512-CSmMZchr+446r79NJ/pjD2yfjqNqFV7k8BnqOq4yTZvXsaQLEIn3tsaU45IsPgs4N7g9OBfPUPDdapSQvck2WQ== - dependencies: - "@tanstack/query-core" "4.29.11" - -"@tanstack/query-sync-storage-persister@^4.27.1": - version "4.29.11" - resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.29.11.tgz#577ead9d08588a420a24ff0a8b77de427a2e6ca8" - integrity sha512-JP9U3m9YPkUelcE9+7D6fAEsNEp2ysKsH6qrd0WJtqsuhe5Zwr407NxaQ5uB2ow/MmhPOm8bxzMZw00Y7RagoQ== - dependencies: - "@tanstack/query-persist-client-core" "4.29.11" - "@tanstack/react-query-devtools@^5.4.3": version "5.4.3" resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.4.3.tgz#7df56de0454104c229f25393cf57a957f6245186" @@ -5431,27 +5599,12 @@ dependencies: "@tanstack/query-devtools" "5.4.2" -"@tanstack/react-query-persist-client@^4.28.0": - version "4.29.12" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-4.29.12.tgz#a3d3dc8dc4318689b275f7329db3619b710401e1" - integrity sha512-rh6zZJB+3j8lr+YsEkVadnqmUELmqNFZQzGGsHS5col/YOjYsMe9ppqaUjIMJ2aXnFXye50sbe4KxHhSGoaNVw== - dependencies: - "@tanstack/query-persist-client-core" "4.29.11" - -"@tanstack/react-query@^4.28.0": - version "4.29.12" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.29.12.tgz#de111cf1d6c389b86acacfaf972302914cfa1208" - integrity sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA== - dependencies: - "@tanstack/query-core" "4.29.11" - use-sync-external-store "^1.2.0" - "@tanstack/react-query@^5.4.3": - version "5.4.3" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.4.3.tgz#cf59120690032e44b8c1c4c463cfb43aaad2fc5f" - integrity sha512-4aSOrRNa6yEmf7mws5QPTVMn8Lp7L38tFoTZ0c1ZmhIvbr8GIA0WT7X5N3yz/nuK8hUtjw9cAzBr4BPDZZ+tzA== + version "5.25.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.25.0.tgz#f4dac794cf10dd956aa56dbbdf67049a5ba2669d" + integrity sha512-u+n5R7mLO7RmeiIonpaCRVXNRWtZEef/aVZ/XGWRPa7trBIvGtzlfo0Ah7ZtnTYfrKEVwnZ/tzRCBcoiqJ/tFw== dependencies: - "@tanstack/query-core" "5.4.3" + "@tanstack/query-core" "5.25.0" "@testing-library/dom@^9.0.0": version "9.3.0" @@ -5598,13 +5751,6 @@ dependencies: "@types/node" "*" -"@types/connect@^3.4.33": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - "@types/content-disposition@*": version "0.5.8" resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.8.tgz#6742a5971f490dc41e59d277eee71361fea0b537" @@ -5847,6 +5993,11 @@ dependencies: "@types/ms" "*" +"@types/dom-screen-wake-lock@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/dom-screen-wake-lock/-/dom-screen-wake-lock-1.0.3.tgz#c3588a5f6f40fae957f9ce5be9bc4927a61bb9a0" + integrity sha512-3Iten7X3Zgwvk6kh6/NRdwN7WbZ760YgFCsF5AxDifltUQzW1RaW+WRmcVtgwFzLjaNu64H+0MPJ13yRa8g3Dw== + "@types/dom-to-image@^2.6.4": version "2.6.4" resolved "https://registry.yarnpkg.com/@types/dom-to-image/-/dom-to-image-2.6.4.tgz#008411e23903cb0ee9e51a42ab8358c609541ee8" @@ -6149,11 +6300,6 @@ dependencies: undici-types "~5.26.4" -"@types/node@^12.12.54": - version "12.20.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" - integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== - "@types/papaparse@^5.3.5": version "5.3.5" resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.5.tgz#e5ad94b1fe98e2a8ea0b03284b83d2cb252bbf39" @@ -6288,6 +6434,13 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== +"@types/secp256k1@^4.0.4": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" + integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== + dependencies: + "@types/node" "*" + "@types/semver@^7.3.12": version "7.3.13" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" @@ -6354,13 +6507,6 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== -"@types/ws@^7.4.4": - version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== - dependencies: - "@types/node" "*" - "@types/ws@^8.5.3": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -6699,35 +6845,31 @@ resolved "https://registry.yarnpkg.com/@wagmi/chains/-/chains-1.2.0.tgz#d59eaa70ec51a5fdcd113975926992acfb17ab12" integrity sha512-dmDRipsE54JfyudOBkuhEexqQWcrZqxn/qiujG8SBzMh/az/AH5xlJSA+j1CPWTx9+QofSMF3B7A4gb6XRmSaQ== -"@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== +"@wagmi/connectors@4.1.22": + version "4.1.22" + resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-4.1.22.tgz#6e2b253a5e08ff8a145cfc2beb943a375a073414" + integrity sha512-9t14G28awyeWTjGS/OfEd/DhdeX5t4Pio3SJHw4M4aTexIfN5DHGmmtfUtSu6DP1r+LD2vSI4ShT7vH0dgtZwA== dependencies: - "@coinbase/wallet-sdk" "^3.6.6" - "@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" + "@coinbase/wallet-sdk" "3.9.1" + "@metamask/sdk" "0.14.3" + "@safe-global/safe-apps-provider" "0.18.1" + "@safe-global/safe-apps-sdk" "8.1.0" + "@walletconnect/ethereum-provider" "2.11.2" "@walletconnect/modal" "2.6.2" - "@walletconnect/utils" "2.10.2" - abitype "0.8.7" - eventemitter3 "^4.0.7" -"@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== +"@wagmi/core@2.6.13": + version "2.6.13" + resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-2.6.13.tgz#eaca669b6b4283ec69412a206e66182a11d926c6" + integrity sha512-RZQ0mBXMH5oe7uvFkNh0e/gPWWLsbkDv/6/SeTY2H7fsH4I/T62ID6eNYUtyOoz7FgQTdAyPdSEt9lst1ZXxkQ== dependencies: - "@wagmi/connectors" "3.1.10" - abitype "0.8.7" - eventemitter3 "^4.0.7" - zustand "^4.3.1" + eventemitter3 "5.0.1" + mipd "0.0.5" + zustand "4.4.1" -"@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== +"@walletconnect/core@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.11.2.tgz#35286be92c645fa461fecc0dfe25de9f076fca8f" + integrity sha512-bB4SiXX8hX3/hyBfVPC5gwZCXCl+OPj+/EDVM71iAO3TDsh78KPbrVAbDnnsbHzZVHlsMohtXX3j5XVsheN3+g== dependencies: "@walletconnect/heartbeat" "1.2.1" "@walletconnect/jsonrpc-provider" "1.0.13" @@ -6740,33 +6882,13 @@ "@walletconnect/relay-auth" "^1.0.4" "@walletconnect/safe-json" "^1.0.2" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.10.6" - "@walletconnect/utils" "2.10.6" + "@walletconnect/types" "2.11.2" + "@walletconnect/utils" "2.11.2" events "^3.3.0" + isomorphic-unfetch "3.1.0" lodash.isequal "4.5.0" uint8arrays "^3.1.0" -"@walletconnect/crypto@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@walletconnect/crypto/-/crypto-1.0.3.tgz#7b8dd4d7e2884fe3543c7c07aea425eef5ef9dd4" - integrity sha512-+2jdORD7XQs76I2Odgr3wwrtyuLUXD/kprNVsjWRhhhdO9Mt6WqVzOPu0/t7OHSmgal8k7SoBQzUc5hu/8zL/g== - dependencies: - "@walletconnect/encoding" "^1.0.2" - "@walletconnect/environment" "^1.0.1" - "@walletconnect/randombytes" "^1.0.3" - aes-js "^3.1.2" - hash.js "^1.1.7" - tslib "1.14.1" - -"@walletconnect/encoding@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@walletconnect/encoding/-/encoding-1.0.2.tgz#cb3942ad038d6a6bf01158f66773062dd25724da" - integrity sha512-CrwSBrjqJ7rpGQcTL3kU+Ief+Bcuu9PH6JLOb+wM6NITX1GTxR/MfNwnQfhLKK6xpRAyj2/nM04OOH6wS8Imag== - dependencies: - is-typedarray "1.0.0" - tslib "1.14.1" - typedarray-to-buffer "3.1.5" - "@walletconnect/environment@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@walletconnect/environment/-/environment-1.0.1.tgz#1d7f82f0009ab821a2ba5ad5e5a7b8ae3b214cd7" @@ -6774,20 +6896,20 @@ dependencies: tslib "1.14.1" -"@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== +"@walletconnect/ethereum-provider@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.11.2.tgz#914f773e37a879bc00cf367437c4e98a826247b1" + integrity sha512-BUDqee0Uy2rCZVkW5Ao3q6Ado/3fePYnFdryVF+YL6bPhj+xQZ5OfKodl+uvs7Rwq++O5wTX2RqOTzpW7+v+Mg== 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/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" + "@walletconnect/modal" "^2.6.2" + "@walletconnect/sign-client" "2.11.2" + "@walletconnect/types" "2.11.2" + "@walletconnect/universal-provider" "2.11.2" + "@walletconnect/utils" "2.11.2" events "^3.3.0" "@walletconnect/events@^1.0.1": @@ -6807,16 +6929,6 @@ "@walletconnect/time" "^1.0.2" tslib "1.14.1" -"@walletconnect/jsonrpc-http-connection@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.4.tgz#aeb0f7eae6565dd031f01d650ee73d358d760ee2" - integrity sha512-ji79pspdBhmIbTwve383tMaDu5Le9plW+oj5GE2aqzxIl3ib8JvRBZRn5lGEBGqVCvqB3MBJL7gBlEwpyRtoxQ== - dependencies: - "@walletconnect/jsonrpc-utils" "^1.0.4" - "@walletconnect/safe-json" "^1.0.1" - cross-fetch "^3.1.4" - tslib "1.14.1" - "@walletconnect/jsonrpc-http-connection@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.7.tgz#a6973569b8854c22da707a759d241e4f5c2d5a98" @@ -6836,15 +6948,6 @@ "@walletconnect/safe-json" "^1.0.2" tslib "1.14.1" -"@walletconnect/jsonrpc-provider@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.6.tgz#e91321ef523f1904e6634e7866a0f3c6f056d2cd" - integrity sha512-f5vQxr53vUVQ51/9mRLb1OiNciT/546XZ68Byn9OYnDBGeGJXK2kQWDHp8sPWZbN5x0p7B6asdCWMVFJ6danlw== - dependencies: - "@walletconnect/jsonrpc-utils" "^1.0.4" - "@walletconnect/safe-json" "^1.0.1" - tslib "1.14.1" - "@walletconnect/jsonrpc-types@1.0.3", "@walletconnect/jsonrpc-types@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz#65e3b77046f1a7fa8347ae02bc1b841abe6f290c" @@ -6870,15 +6973,6 @@ "@walletconnect/jsonrpc-types" "^1.0.3" tslib "1.14.1" -"@walletconnect/jsonrpc-utils@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.4.tgz#2009ba3907b02516f2caacd2fb871ff0d472b2cb" - integrity sha512-y0+tDxcTZ9BHBBKBJbjZxLUXb+zQZCylf7y/jTvDPNx76J0hYYc+F9zHzyqBLeorSKepLTk6yI8hw3NXbAQB3g== - dependencies: - "@walletconnect/environment" "^1.0.1" - "@walletconnect/jsonrpc-types" "^1.0.2" - tslib "1.14.1" - "@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" @@ -6889,14 +6983,6 @@ events "^3.3.0" ws "^7.5.1" -"@walletconnect/keyvaluestorage@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.0.2.tgz#92f5ca0f54c1a88a093778842ce0c874d86369c8" - integrity sha512-U/nNG+VLWoPFdwwKx0oliT4ziKQCEoQ27L5Hhw8YOFGA2Po9A9pULUYNWhDgHkrb0gYDNt//X7wABcEWWBd3FQ== - dependencies: - 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" @@ -6906,66 +6992,6 @@ 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" - integrity sha512-v5L7rYk9loVnfvUf0mF+76bUPFaU5/Vh7mzL6/950CD/yoGdzYZ3Kj+L7mkC6HPMEGeQsBP1+sqBuiVGZ/aODA== - dependencies: - "@walletconnect/crypto" "^1.0.3" - "@walletconnect/encoding" "^1.0.2" - "@walletconnect/jsonrpc-utils" "^1.0.4" - "@walletconnect/legacy-types" "^2.0.0" - "@walletconnect/legacy-utils" "^2.0.0" - "@walletconnect/safe-json" "^1.0.1" - "@walletconnect/window-getters" "^1.0.1" - "@walletconnect/window-metadata" "^1.0.1" - detect-browser "^5.3.0" - query-string "^6.13.5" - -"@walletconnect/legacy-modal@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@walletconnect/legacy-modal/-/legacy-modal-2.0.0.tgz#d0fab01a1337a8f5d88cdb1430cbef2d46072bbf" - integrity sha512-jckNd8lMhm4X7dX9TDdxM3bXKJnaqkRs6K2Mo5j6GmbIF9Eyx40jZ5+q457RVxvM6ciZEDT5s1wBHWdWoOo+9Q== - dependencies: - "@walletconnect/legacy-types" "^2.0.0" - "@walletconnect/legacy-utils" "^2.0.0" - copy-to-clipboard "^3.3.3" - preact "^10.12.0" - qrcode "^1.5.1" - -"@walletconnect/legacy-provider@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@walletconnect/legacy-provider/-/legacy-provider-2.0.0.tgz#08e2db1e4c234743b2f30422bc8100bc42e8fc44" - integrity sha512-A8xPebMI1A+50HbWwTpFCbwP7G+1NGKdTKyg8BUUg3h3Y9JucpC1W6w/x0v1Xw7qFEqQnz74LoIN/A3ytH9xrQ== - dependencies: - "@walletconnect/jsonrpc-http-connection" "^1.0.4" - "@walletconnect/jsonrpc-provider" "^1.0.6" - "@walletconnect/legacy-client" "^2.0.0" - "@walletconnect/legacy-modal" "^2.0.0" - "@walletconnect/legacy-types" "^2.0.0" - "@walletconnect/legacy-utils" "^2.0.0" - -"@walletconnect/legacy-types@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@walletconnect/legacy-types/-/legacy-types-2.0.0.tgz#224278ae2874c6a2ca805c2d1d062a511dcf7227" - integrity sha512-sOVrA7HUdbI1OwKyPOQU0/DdvTSVFlsXWpAk2K2WvP2erTkBWPMTJq6cv2BmKdoJ3p6gLApT7sd+jHi3OF71uw== - dependencies: - "@walletconnect/jsonrpc-types" "^1.0.2" - -"@walletconnect/legacy-utils@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@walletconnect/legacy-utils/-/legacy-utils-2.0.0.tgz#e3a637c00783f9cd2ae139b640f82223ab78ed9d" - integrity sha512-CPWxSVVXw0kgNCxvU126g4GiV3mzXmC8IPJ15twE46aJ1FX+RHEIfAzFMFz2F2+fEhBxL63A7dwNQKDXorRPcQ== - dependencies: - "@walletconnect/encoding" "^1.0.2" - "@walletconnect/jsonrpc-utils" "^1.0.4" - "@walletconnect/legacy-types" "^2.0.0" - "@walletconnect/safe-json" "^1.0.1" - "@walletconnect/window-getters" "^1.0.1" - "@walletconnect/window-metadata" "^1.0.1" - detect-browser "^5.3.0" - query-string "^6.13.5" - "@walletconnect/logger@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@walletconnect/logger/-/logger-2.0.1.tgz#7f489b96e9a1ff6bf3e58f0fbd6d69718bf844a8" @@ -6991,7 +7017,7 @@ motion "10.16.2" qrcode "1.5.3" -"@walletconnect/modal@2.6.2", "@walletconnect/modal@^2.4.3": +"@walletconnect/modal@2.6.2", "@walletconnect/modal@^2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.6.2.tgz#4b534a836f5039eeb3268b80be7217a94dd12651" integrity sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA== @@ -6999,16 +7025,6 @@ "@walletconnect/modal-core" "2.6.2" "@walletconnect/modal-ui" "2.6.2" -"@walletconnect/randombytes@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@walletconnect/randombytes/-/randombytes-1.0.3.tgz#e795e4918367fd1e6a2215e075e64ab93e23985b" - integrity sha512-35lpzxcHFbTN3ABefC9W+uBpNZl1GC4Wpx0ed30gibfO/y9oLdy1NznbV96HARQKSBV9J9M/rrtIvf6a23jfYw== - dependencies: - "@walletconnect/encoding" "^1.0.2" - "@walletconnect/environment" "^1.0.1" - randombytes "^2.1.0" - tslib "1.14.1" - "@walletconnect/relay-api@^1.0.9": version "1.0.9" resolved "https://registry.yarnpkg.com/@walletconnect/relay-api/-/relay-api-1.0.9.tgz#f8c2c3993dddaa9f33ed42197fc9bfebd790ecaf" @@ -7043,19 +7059,19 @@ dependencies: tslib "1.14.1" -"@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== +"@walletconnect/sign-client@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.11.2.tgz#855609653855f0d23b0502cdbdcf43402e34c459" + integrity sha512-MfBcuSz2GmMH+P7MrCP46mVE5qhP0ZyWA0FyIH6/WuxQ6G+MgKsGfaITqakpRPsykWOJq8tXMs3XvUPDU413OQ== dependencies: - "@walletconnect/core" "2.10.6" + "@walletconnect/core" "2.11.2" "@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.10.6" - "@walletconnect/utils" "2.10.6" + "@walletconnect/types" "2.11.2" + "@walletconnect/utils" "2.11.2" events "^3.3.0" "@walletconnect/time@^1.0.2": @@ -7065,22 +7081,10 @@ dependencies: tslib "1.14.1" -"@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" - "@walletconnect/jsonrpc-types" "1.0.3" - "@walletconnect/keyvaluestorage" "^1.0.2" - "@walletconnect/logger" "^2.0.1" - events "^3.3.0" - -"@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== +"@walletconnect/types@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.11.2.tgz#d0359dd4106fcaa1634241a00428d3ea08d0d3c7" + integrity sha512-p632MFB+lJbip2cvtXPBQslpUdiw1sDtQ5y855bOlAGquay+6fZ4h1DcDePeKQDQM3P77ax2a9aNPZxV6y/h1Q== dependencies: "@walletconnect/events" "^1.0.1" "@walletconnect/heartbeat" "1.2.1" @@ -7089,25 +7093,25 @@ "@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== +"@walletconnect/universal-provider@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.11.2.tgz#bec3038f51445d707bbec75f0cb8af0a1f1e04db" + integrity sha512-cNtIn5AVoDxKAJ4PmB8m5adnf5mYQMUamEUPKMVvOPscfGtIMQEh9peKsh2AN5xcRVDbgluC01Id545evFyymw== 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.10.6" - "@walletconnect/types" "2.10.6" - "@walletconnect/utils" "2.10.6" + "@walletconnect/sign-client" "2.11.2" + "@walletconnect/types" "2.11.2" + "@walletconnect/utils" "2.11.2" events "^3.3.0" -"@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== +"@walletconnect/utils@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.11.2.tgz#dee0f19adf5e38543612cbe9fa4de7ed28eb7e85" + integrity sha512-LyfdmrnZY6dWqlF4eDrx5jpUwsB2bEPjoqR5Z6rXPiHJKUOdJt7az+mNOn5KTSOlRpd1DmozrBrWr+G9fFLYVw== dependencies: "@stablelib/chacha20poly1305" "1.0.1" "@stablelib/hkdf" "1.0.1" @@ -7117,27 +7121,7 @@ "@walletconnect/relay-api" "^1.0.9" "@walletconnect/safe-json" "^1.0.2" "@walletconnect/time" "^1.0.2" - "@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/types" "2.11.2" "@walletconnect/window-getters" "^1.0.1" "@walletconnect/window-metadata" "^1.0.1" detect-browser "5.3.0" @@ -7159,100 +7143,102 @@ "@walletconnect/window-getters" "^1.0.1" tslib "1.14.1" -"@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== +"@web3modal/common@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/common/-/common-4.1.3.tgz#b31a60dbfb2b02708ddef7b896bd2992534ede09" + integrity sha512-xxNS0+J88TQHNKxHk57uEIb1J03OI9n9CulaOexHv7oPp2x6D+vb5FYdJj/Oxskq6osYKg+LVupCSEaJEr6bQw== 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== +"@web3modal/core@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/core/-/core-4.1.3.tgz#92a1a2a7c00b4f9d8221c20adce3a1ce908fb662" + integrity sha512-T8J9vfxiDrnRQxuqRQtfE1siBSf7WQxgyfvgPfUmAtnzAYVpQ5eAL4JEUTp6KrOyB070ujT5fmfBjz8di8agQg== dependencies: - "@web3modal/common" "3.5.0" - valtio "1.12.1" + "@web3modal/common" "4.1.3" + "@web3modal/wallet" "4.1.3" + valtio "1.11.2" -"@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== +"@web3modal/polyfills@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/polyfills/-/polyfills-4.1.3.tgz#444fd1d7a942ba91592533d34cee6dcf16f19ef9" + integrity sha512-CTT1A0B43Ae4uSm8GW4F6Qamhx3tcIC0/yyOSW40BaRvd10K2Imm50K3I8jLFS8HG1ZzqZe+pOV2P/OyOwPU4g== dependencies: buffer "6.0.3" -"@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== +"@web3modal/scaffold-react@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold-react/-/scaffold-react-4.1.3.tgz#72b82927f1c6756bf83daa456187da090b549d65" + integrity sha512-zx/jVqY6MFjOZWj/X9RUpSaTZ5Kz+dmH6wuOGSdlyUSYvUV5hJgUGEKGjCr5AVBk67n67lHKFUy1nxS9GXvVdg== dependencies: - "@web3modal/scaffold" "3.5.0" + "@web3modal/scaffold" "4.1.3" -"@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== +"@web3modal/scaffold-utils@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold-utils/-/scaffold-utils-4.1.3.tgz#c751ce47babfef98d53d1c46aaae5b50a0d28537" + integrity sha512-1rNZkTjhpvVRPXm8RKz51E/T9Ujhjh8DPxT3oQFnTNlws98yU+RLDJYrR8rQoqJfmDK3E5dViLmxjIbrLKh/dw== dependencies: - "@web3modal/polyfills" "3.5.0" - "@web3modal/scaffold" "3.5.0" + "@web3modal/core" "4.1.3" + "@web3modal/polyfills" "4.1.3" + valtio "1.11.2" -"@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== +"@web3modal/scaffold-vue@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold-vue/-/scaffold-vue-4.1.3.tgz#d8e827c8431526a86cc6fbb7430e3fb7d560b9b0" + integrity sha512-xHySyt2A01VYGtQ3qHKEyv5n+JGOnZ41/ZM8glK6yfe4nDNS0c9X/psPoeVwWvLhOYPKWCikkgGvB1TmoyjNiQ== dependencies: - "@web3modal/scaffold" "3.5.0" + "@web3modal/scaffold" "4.1.3" -"@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== +"@web3modal/scaffold@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/scaffold/-/scaffold-4.1.3.tgz#2a51ad18d6522233af71b3c4e8994ac3d9905a14" + integrity sha512-daOCaa8h4AT9BGqbsBAAU0CQL/hFWt9Bs6CK9zjesvhwxK2qQZ2O/t8tU2c68Gsj6VRDim7CcWCKjNYAqtGNyQ== dependencies: - "@web3modal/core" "3.5.0" - "@web3modal/ui" "3.5.0" + "@web3modal/common" "4.1.3" + "@web3modal/core" "4.1.3" + "@web3modal/ui" "4.1.3" lit "3.1.0" + optionalDependencies: + "@web3modal/siwe" "4.1.3" -"@web3modal/siwe@*": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@web3modal/siwe/-/siwe-3.5.0.tgz#5f7fa6a7209c6ddddcd008ad47bb4dc3815d4a37" - integrity sha512-i9AvSH3uA93lu7z79d90aMaHx3Kn/EhblRJa2NOykADuAZX3UeXkX9KX7pkLVEYD1TkQN4mOHTFRQ6LeXq9EcA== +"@web3modal/siwe@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/siwe/-/siwe-4.1.3.tgz#e00438a688617cf4a65f6ec62437b4e7178b7ec8" + integrity sha512-YjCFvoUyjjkTmRLyaDuhCaWpz9/gqQNZRIXwkDg/0AZY92WeVq0jJQJIWmFv6Ag16nakqo73vtHjUGfPC4bbFQ== dependencies: - "@web3modal/core" "3.5.0" - "@web3modal/scaffold-utils" "3.5.0" - optionalDependencies: - react ">=17" - react-dom ">=17" - vue ">=3" + "@web3modal/core" "4.1.3" + "@web3modal/scaffold-utils" "4.1.3" + lit "3.1.0" + valtio "1.11.2" -"@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== +"@web3modal/ui@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/ui/-/ui-4.1.3.tgz#b7ca4a28afc8e8b2910e47d2501eafe92a5a1173" + integrity sha512-gZBkpq186iPC05jYBi58MXYH4b3qxo+LIYBdXz++qvaZRiSV894hvHpVLTjJgJivUiP0Lzt/I+u63/G4dkwSgA== dependencies: 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" +"@web3modal/wagmi@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/wagmi/-/wagmi-4.1.3.tgz#fe3f7dacdf2057ef33ee0272452c533edbcf2567" + integrity sha512-YFYYFmp2HoXIo7mfL/eWc//F9cqIyHdKgUZmSYArDWxNze/MOuCrShnZWarXa1fVl7kY3Cq+bZpExM8TcttQCw== + dependencies: + "@web3modal/polyfills" "4.1.3" + "@web3modal/scaffold" "4.1.3" + "@web3modal/scaffold-react" "4.1.3" + "@web3modal/scaffold-utils" "4.1.3" + "@web3modal/scaffold-vue" "4.1.3" optionalDependencies: - "@web3modal/siwe" "*" + "@web3modal/siwe" "4.1.3" 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== +"@web3modal/wallet@4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@web3modal/wallet/-/wallet-4.1.3.tgz#b38a1e96ff3f87b555075a2581154152de7ac41f" + integrity sha512-wcfljBc2c+0VF5d1hfVWRDrnRdlNGGTPz0iky6tjQW57sam9Yg5IKAbrEqyDZZs0zrAz8K03yHgKnP1BbQHc5A== dependencies: zod "3.22.4" @@ -7276,14 +7262,6 @@ resolved "https://registry.yarnpkg.com/@zag-js/focus-visible/-/focus-visible-0.2.2.tgz#56233480ca1275d3218fb2e10696a33d1a6b9e64" integrity sha512-0j2gZq8HiZ51z4zNnSkF1iSkqlwRDvdH+son3wHdoz+7IUdMN/5Exd4TxMJ+gq2Of1DiXReYLL9qqh2PdQ4wgA== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -7294,16 +7272,16 @@ abitype@0.8.11: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.8.11.tgz#66e1cf2cbf46f48d0e57132d7c1c392447536cc1" integrity sha512-bM4v2dKvX08sZ9IU38IN5BKmN+ZkOSd2oI4a9f0ejHYZQYV6cDr7j+d95ga0z2XHG36Y4jzoG5Z7qDqxp7fi/A== -abitype@0.8.7: - version "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== +abitype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" + integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -7354,11 +7332,6 @@ 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.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" - integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -7366,15 +7339,6 @@ agent-base@6: dependencies: debug "4" -agentkeepalive@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== - dependencies: - debug "^4.1.0" - depd "^1.1.2" - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -7747,25 +7711,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-x@^3.0.2: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== - dependencies: - safe-buffer "^5.0.1" - base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -bigint-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" - integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== - dependencies: - bindings "^1.3.0" - bignumber.js@^9.0.0: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" @@ -7781,17 +7731,10 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bind-decorator@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/bind-decorator/-/bind-decorator-1.0.11.tgz#e41bc06a1f65dd9cec476c91c5daf3978488252f" - integrity sha512-yzkH0uog6Vv/vQ9+rhSKxecnqGUZHYncg7qS7voz3Q76+TAi1SGiOKk2mlOvusQnFz9Dc4BC/NMkeXu11YgjJg== - -bindings@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" +bintrees@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== bl@^4.0.3: version "4.1.0" @@ -7807,7 +7750,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.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== @@ -7817,14 +7765,10 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -borsh@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" - integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== - dependencies: - bn.js "^5.2.0" - bs58 "^4.0.0" - text-encoding-utf-8 "^1.0.2" +bowser@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== brace-expansion@^1.1.7: version "1.1.11" @@ -7848,6 +7792,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" @@ -7865,13 +7814,6 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" -bs58@^4.0.0, bs58@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== - dependencies: - base-x "^3.0.2" - bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -7884,15 +7826,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" - integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: +buffer@6.0.3, buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -7908,10 +7842,10 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -bufferutil@^4.0.1: - version "4.0.7" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" - integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== +bufferutil@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== dependencies: node-gyp-build "^4.3.0" @@ -8121,7 +8055,7 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clsx@^1.1.0: +clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -8269,7 +8203,7 @@ cookie@~0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -copy-to-clipboard@3.3.3, copy-to-clipboard@^3.2.0, copy-to-clipboard@^3.3.1, copy-to-clipboard@^3.3.3: +copy-to-clipboard@3.3.3, copy-to-clipboard@^3.2.0, copy-to-clipboard@^3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== @@ -8333,6 +8267,13 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "2.6.7" +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + 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" @@ -8729,10 +8670,10 @@ 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.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.0.tgz#f64b1c700eacd2e8a44404cfdcbecbb34afbc436" - integrity sha512-0C40H1mZ1j0ySJYnAG5dCvkfGfB7mtwOcF/rpD5MlYdjnua9qrR+SkoOyJiJvcQUZ1JY6MG0VDwm+00khANVQw== +dappscout-iframe@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9" + integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw== dependencies: react "^18.2.0" react-dom "^18.2.0" @@ -8752,6 +8693,13 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" +date-fns@^2.29.3: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + dateformat@^4.6.3: version "4.6.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" @@ -8767,7 +8715,7 @@ dayjs@^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.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, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -8798,7 +8746,7 @@ decimal.js@^10.4.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e" integrity sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA== -decode-uri-component@^0.2.0, decode-uri-component@^0.2.2: +decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== @@ -8881,11 +8829,6 @@ delaunator@5: dependencies: robust-predicates "^3.0.0" -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -8896,22 +8839,12 @@ denque@^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: +detect-browser@5.3.0, detect-browser@^5.2.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== @@ -9107,11 +9040,33 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +eciesjs@^0.3.15, eciesjs@^0.3.16: + version "0.3.18" + resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.3.18.tgz#67b5d73a8466e40a45bbc2f2a3177e71e9c0643d" + integrity sha512-RQhegEtLSyIiGJmFTZfvCTHER/fymipXFVx6OwSRYD6hOuy+6Kjpk0dGvIfP9kxn/smBpxQy71uxpGO406ITCw== + dependencies: + "@types/secp256k1" "^4.0.4" + futoin-hkdf "^1.5.3" + secp256k1 "^5.0.0" + electron-to-chromium@^1.4.251: version "1.4.276" 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.5" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" + integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== + 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" @@ -9132,13 +9087,29 @@ encode-utf8@^1.0.3: resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== -end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: +end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" +engine.io-client@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.3.tgz#4cf6fa24845029b238f83c628916d9149c399bc5" + integrity sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49" + integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw== + enhanced-resolve@^5.12.0: version "5.12.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" @@ -9297,18 +9268,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== - dependencies: - es6-promise "^4.0.3" - esbuild@^0.18.10: version "0.18.20" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" @@ -9342,16 +9301,16 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -9673,22 +9632,23 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eth-block-tracker@6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-6.1.0.tgz#0481f97bbb88a100b9d45806fe7e37af741cbefc" - integrity sha512-K9SY8+/xMBi4M5HHTDdxnpEqEEGjbNpzHFqvxyjMZej8InV/B+CkFRKM6W+uvrFJ7m8Zd1E0qUkseU3vdIDFYQ== +eth-block-tracker@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-7.1.0.tgz#dfc16085c6817cc30caabba381deb8d204c1c766" + integrity sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg== dependencies: - "@metamask/safe-event-emitter" "^2.0.0" - "@metamask/utils" "^3.0.1" + "@metamask/eth-json-rpc-provider" "^1.0.0" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^5.0.1" json-rpc-random-id "^1.0.1" pify "^3.0.0" -eth-json-rpc-filters@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-5.1.0.tgz#f0c2aeaec2a45e2dc6ca1b9843d8e85447821427" - integrity sha512-fos+9xmoa1A2Ytsc9eYof17r81BjdJOUcGcgZn4K/tKdCCTb+a8ytEtwlu1op5qsXFDlgGmstTELFrDEc89qEQ== +eth-json-rpc-filters@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz#0b3e370f017f5c6f58d3e7bd0756d8099ed85c56" + integrity sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig== dependencies: - "@metamask/safe-event-emitter" "^2.0.0" + "@metamask/safe-event-emitter" "^3.0.0" async-mutex "^0.2.6" eth-query "^2.1.2" json-rpc-engine "^6.1.0" @@ -9702,14 +9662,7 @@ eth-query@^2.1.2: json-rpc-random-id "^1.0.0" xtend "^4.0.1" -eth-rpc-errors@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.2.tgz#11bc164e25237a679061ac05b7da7537b673d3b7" - integrity sha512-n+Re6Gu8XGyfFy1it0AwbD1x0MUzspQs0D5UiPs1fFPCr6WAwZM+vbIhXheBFrpgosqN9bs5PqlB4Q61U/QytQ== - dependencies: - fast-safe-stringify "^2.0.6" - -eth-rpc-errors@^4.0.2: +eth-rpc-errors@^4.0.2, eth-rpc-errors@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg== @@ -9731,10 +9684,15 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter2@^6.4.5, eventemitter2@^6.4.7: + version "6.4.9" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" + integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== + +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== events@^3.3.0: version "3.3.0" @@ -9804,11 +9762,6 @@ extension-port-stream@^2.0.1: dependencies: webextension-polyfill-ts "^0.22.0" -eyes@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== - fast-copy@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.0.tgz#875ebf33b13948ae012b6e51d33da5e6e7571ab8" @@ -9860,11 +9813,6 @@ fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fast-stable-stringify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" - integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== - fast-url-parser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" @@ -9908,11 +9856,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -10107,6 +10050,11 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +futoin-hkdf@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz#6c8024f2e1429da086d4e18289ef2239ad33ee35" + integrity sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ== + gaxios@^5.0.0: version "5.1.3" resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.1.3.tgz#f7fa92da0fe197c846441e5ead2573d4979e9013" @@ -10468,7 +10416,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash.js@^1.1.7: +hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -10532,6 +10480,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" @@ -10561,6 +10518,13 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -10593,18 +10557,25 @@ human-signals@^3.0.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - husky@^8.0.0: version "8.0.1" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== +i18next-browser-languagedetector@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz#de0321cba6881be37d82e20e4d6f05aa75f6e37f" + integrity sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA== + dependencies: + "@babel/runtime" "^7.23.2" + +i18next@22.5.1: + version "22.5.1" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.5.1.tgz#99df0b318741a506000c243429a7352e5f44d424" + integrity sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA== + dependencies: + "@babel/runtime" "^7.20.6" + iconv-lite@0.6, iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -10686,7 +10657,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -10719,7 +10690,7 @@ internal-slot@^1.0.4: resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== -invariant@^2.2.2, invariant@^2.2.4: +invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -10912,6 +10883,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -10989,11 +10965,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" -is-typedarray@1.0.0, is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -11041,16 +11012,19 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic-unfetch@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + isomorphic-ws@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== -isomorphic-ws@^4.0.1: - version "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" @@ -11118,25 +11092,6 @@ jaeger-client@^3.15.0: uuid "^8.3.2" xorshift "^1.1.1" -jayson@^3.4.4: - version "3.7.0" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.7.0.tgz#b735b12d06d348639ae8230d7a1e2916cb078f25" - integrity sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ== - dependencies: - "@types/connect" "^3.4.33" - "@types/node" "^12.12.54" - "@types/ws" "^7.4.4" - JSONStream "^1.3.5" - commander "^2.20.3" - delay "^5.0.0" - es6-promisify "^5.0.0" - eyes "^0.1.8" - isomorphic-ws "^4.0.1" - json-stringify-safe "^5.0.1" - lodash "^4.17.20" - uuid "^8.3.2" - ws "^7.4.5" - jest-changed-files@^29.2.0: version "29.2.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.2.0.tgz#b6598daa9803ea6a4dce7968e20ab380ddbee289" @@ -11646,7 +11601,7 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-rpc-engine@6.1.0, json-rpc-engine@^6.1.0: +json-rpc-engine@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz#bf5ff7d029e1c1bf20cb6c0e9f348dcd8be5a393" integrity sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ== @@ -11684,11 +11639,6 @@ json-stable-stringify@^1.0.2: dependencies: jsonify "^0.0.1" -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json5@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -11725,11 +11675,6 @@ jsonify@^0.0.1: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== - "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" @@ -11738,10 +11683,10 @@ jsonparse@^1.2.0: array-includes "^3.1.5" object.assign "^4.1.3" -keccak@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.3.tgz#4bc35ad917be1ef54ff246f904c2bbbf9ac61276" - integrity sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ== +keccak@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" + integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== dependencies: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" @@ -12127,6 +12072,13 @@ memoize-one@^6.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== +merge-options@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== + dependencies: + is-plain-obj "^2.1.0" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -12199,11 +12151,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" @@ -12247,6 +12204,13 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== +mipd@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mipd/-/mipd-0.0.5.tgz#367ee796531c23f0631f129038700b1406663aec" + integrity sha512-gbKA784D2WKb5H/GtqEv+Ofd1S9Zj+Z/PGDIl1u1QAbswkxD28BQ5bSXQxkeBzPBABg1iDSbiwGG1XqlOxRspA== + dependencies: + viem "^1.1.4" + mixpanel-browser@^2.47.0: version "2.47.0" resolved "https://registry.yarnpkg.com/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz#4e7fd3bb660c6f63443efbd169d1cd327db71ed4" @@ -12314,7 +12278,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1: +ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -12415,6 +12379,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@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + 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" @@ -12438,13 +12407,20 @@ node-fetch-native@^1.4.0, node-fetch-native@^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: +node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.9: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.11: version "2.6.11" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" @@ -12452,13 +12428,6 @@ node-fetch@^2.6.11: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.7, node-fetch@^2.6.9: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-fetch@^3.2.9: version "3.2.10" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" @@ -12532,6 +12501,15 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== +obj-multiplex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/obj-multiplex/-/obj-multiplex-1.0.0.tgz#2f2ae6bfd4ae11befe742ea9ea5b36636eabffc1" + integrity sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA== + dependencies: + end-of-stream "^1.4.0" + once "^1.4.0" + readable-stream "^2.3.3" + object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -13091,6 +13069,11 @@ pngjs@^5.0.0: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== +pony-cause@^2.1.10: + version "2.1.10" + resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.10.tgz#828457ad6f13be401a075dbf14107a9057945174" + integrity sha512-3IKLNXclQgkU++2fSi93sQ6BznFuxSLB11HdvZQ6JW/spahf/P1pAHBQEahr20rs0htZW0UDkM1HmA+nZkXKsw== + popmotion@11.0.3: version "11.0.3" resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9" @@ -13231,15 +13214,10 @@ postgres-range@^1.1.1: resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== -preact@^10.12.0: - version "10.15.1" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.15.1.tgz#a1de60c9fc0c79a522d969c65dcaddc5d994eede" - integrity sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g== - -preact@^10.5.9: - version "10.11.3" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19" - integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg== +preact@^10.16.0: + version "10.19.6" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.6.tgz#66007b67aad4d11899f583df1b0116d94a89b8f5" + integrity sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw== prebuild-install@^7.1.1: version "7.1.1" @@ -13297,6 +13275,11 @@ prismjs@~1.27.0: resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -13327,6 +13310,14 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +prom-client@15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-15.1.1.tgz#71ba84371241acd173181b04a436782c246f3652" + integrity sha512-GVA2H96QCg2q71rjc3VYvSrVG7OpnJxyryC7dMzvfJfpJJHzQVwF3TJLfHzKORcwJpElWs1TwXLthlJAFJxq2A== + dependencies: + "@opentelemetry/api" "^1.4.0" + tdigest "^0.1.1" + promise-polyfill@^8.1.3: version "8.3.0" resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63" @@ -13407,6 +13398,23 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +qr-code-styling@^1.6.0-rc.1: + version "1.6.0-rc.1" + resolved "https://registry.yarnpkg.com/qr-code-styling/-/qr-code-styling-1.6.0-rc.1.tgz#6c89e185fa50cc9135101085c12ae95b06f1b290" + integrity sha512-ModRIiW6oUnsP18QzrRYZSc/CFKFKIdj7pUs57AEVH20ajlglRpN3HukjHk0UbNMTlKGuaYl7Gt6/O5Gg2NU2Q== + dependencies: + qrcode-generator "^1.4.3" + +qrcode-generator@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7" + integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw== + +qrcode-terminal-nooctal@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/qrcode-terminal-nooctal/-/qrcode-terminal-nooctal-0.12.1.tgz#45016aca0d82b2818de7af0a06d072ad671fbe2e" + integrity sha512-jy/kkD0iIMDjTucB+5T6KBsnirlhegDH47vHgrj5MejchSQmi/EAMM0xMFeePgV9CJkkAapNakpVUWYgHvtdKg== + qrcode@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" @@ -13434,13 +13442,6 @@ qs@^6.10.2: dependencies: side-channel "^1.0.4" -qs@^6.10.3: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - query-string@7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" @@ -13451,16 +13452,6 @@ query-string@7.1.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" -query-string@^6.13.5: - version "6.14.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -13597,6 +13588,14 @@ react-hook-form@^7.33.1: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.37.0.tgz#4d1738f092d3d8a3ade34ee892d97350b1032b19" integrity sha512-6NFTxsnw+EXSpNNvLr5nFMjPdYKRryQcelTHg7zwBB6vAzfPIcZq4AExP4heVlwdzntepQgwiOQW4z7Mr99Lsg== +react-i18next@^13.2.2: + version "13.5.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.5.0.tgz#44198f747628267a115c565f0c736a50a76b1ab0" + integrity sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA== + dependencies: + "@babel/runtime" "^7.22.5" + html-parse-stringify "^3.0.1" + react-identicons@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/react-identicons/-/react-identicons-1.2.5.tgz#3502249e49d88f4e3500092694410a984bb102fa" @@ -13646,6 +13645,14 @@ react-jazzicon@^1.0.4: dependencies: mersenne-twister "^1.1.0" +react-native-webview@^11.26.0: + version "11.26.1" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.26.1.tgz#658c09ed5162dc170b361e48c2dd26c9712879da" + integrity sha512-hC7BkxOpf+z0UKhxFSFTPAM4shQzYmZHoELa6/8a/MspcjEP7ukYKpuSUTLDywQditT8yI9idfcKvfZDKQExGw== + dependencies: + escape-string-regexp "2.0.0" + invariant "2.2.4" + react-number-format@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.3.1.tgz#840c257da9cb4b248990d8db46e4d23e8bac67ff" @@ -13762,6 +13769,19 @@ react@18.2.0, react@>=17, react@^18.2.0: dependencies: loose-envify "^1.1.0" +readable-stream@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + readable-stream@3.6.2, readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -13784,7 +13804,20 @@ readable-stream@^2.3.3: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.5.0, readable-stream@^3.6.0: +readable-stream@^2.3.7: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -14097,6 +14130,16 @@ robust-predicates@^3.0.0: resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== +rollup-plugin-visualizer@^5.9.2: + version "5.12.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz#661542191ce78ee4f378995297260d0c1efb1302" + integrity sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ== + dependencies: + open "^8.4.0" + picomatch "^2.3.1" + source-map "^0.7.4" + yargs "^17.5.1" + rollup@^3.27.1: version "3.29.4" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" @@ -14104,19 +14147,6 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" -rpc-websockets@^7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.0.tgz#bbeb87572e66703ff151e50af1658f98098e2748" - integrity sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ== - dependencies: - "@babel/runtime" "^7.17.2" - eventemitter3 "^4.0.7" - uuid "^8.3.2" - ws "^8.5.0" - optionalDependencies: - bufferutil "^4.0.1" - utf-8-validate "^5.0.2" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -14129,13 +14159,6 @@ rw@1: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== -rxjs@^6.6.3: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - rxjs@^7.5.5: version "7.5.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" @@ -14153,11 +14176,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-json-utils@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.1.1.tgz#0e883874467d95ab914c3f511096b89bfb3e63b1" - integrity sha512-SAJWGKDs50tAbiDXLf89PDwt9XYkWyANFWVzn4dTXl5QyI8t2o/bW5/OJl3lvc2WVU4MEpTo9Yz5NVFNsp+OJQ== - safe-regex-test@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" @@ -14205,6 +14223,15 @@ scslre@^0.1.6: regexp-ast-analysis "^0.2.3" regexpp "^3.2.0" +secp256k1@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" + integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^5.0.0" + node-gyp-build "^4.2.0" + secure-json-parse@^2.4.0: version "2.5.0" resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.5.0.tgz#f929829df2adc7ccfb53703569894d051493a6ac" @@ -14360,6 +14387,24 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +socket.io-client@^4.5.1: + version "4.7.4" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.4.tgz#5f0e060ff34ac0a4b4c5abaaa88e0d1d928c64c8" + integrity sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + sonic-boom@^2.2.1: version "2.8.0" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" @@ -14397,6 +14442,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -14449,14 +14499,6 @@ std-env@^3.4.3: 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" - integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== - dependencies: - inherits "~2.0.4" - readable-stream "^3.5.0" - stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" @@ -14584,6 +14626,13 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ== + dependencies: + safe-buffer "~5.1.0" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -14665,6 +14714,11 @@ stylis@4.1.3: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + sucrase@^3.20.3: version "3.29.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d" @@ -14677,11 +14731,6 @@ sucrase@^3.20.3: pirates "^4.0.1" ts-interface-checker "^0.1.9" -superstruct@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" - integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== - superstruct@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" @@ -14870,6 +14919,13 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tdigest@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" + integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== + dependencies: + bintrees "1.0.2" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -14879,11 +14935,6 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-encoding-utf-8@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" - integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -14926,7 +14977,7 @@ thriftrw@^3.5.0: error "7.0.2" long "^2.4.0" -"through@>=2.2.7 <3", through@^2.3.8: +through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -15099,7 +15150,7 @@ tsconfig-paths@^4.0.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -15171,13 +15222,6 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typedarray-to-buffer@3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - types-ramda@^0.29.5: version "0.29.5" resolved "https://registry.yarnpkg.com/types-ramda/-/types-ramda-0.29.5.tgz#1cb0488d39eb72723a8f95af9b6dfe483e4f34a7" @@ -15185,10 +15229,10 @@ types-ramda@^0.29.5: dependencies: ts-toolbelt "^9.6.0" -typescript@^5.1.0: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" + integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== typescript@^5.2.2: version "5.3.3" @@ -15255,6 +15299,11 @@ unenv@^1.7.4: node-fetch-native "^1.4.1" pathe "^1.1.1" +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + 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" @@ -15375,15 +15424,15 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== -utf-8-validate@^5.0.2: - version "5.0.10" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" - integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== +utf-8-validate@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.3.tgz#7d8c936d854e86b24d1d655f138ee27d2636d777" + integrity sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA== dependencies: node-gyp-build "^4.3.0" @@ -15430,26 +15479,17 @@ valtio@1.11.2: proxy-compare "2.5.1" use-sync-external-store "1.2.0" -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== +viem@2.9.6: + version "2.9.6" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.9.6.tgz#66819684e878b6ea659ad9c49a0a864512859190" + integrity sha512-VVFWjGQei2wnWTvAB/xrIf22m6flCwxeBr8LnwejXMTSSi1EORWEswrw2lfKTmw3TlRPSG4uSiQMa/d0l0DKRg== 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" + abitype "1.0.0" isows "1.0.3" ws "8.13.0" @@ -15468,7 +15508,7 @@ viem@^1.0.0: isomorphic-ws "5.0.0" ws "8.12.0" -viem@^1.20.3: +viem@^1.1.4, viem@^1.20.3: version "1.21.4" resolved "https://registry.yarnpkg.com/viem/-/viem-1.21.4.tgz#883760e9222540a5a7e0339809202b45fe6a842d" integrity sha512-BNVYdSaUjeS2zKQgPs+49e5JKocfo60Ib2yiXOWBT6LuVxY1I/6fFX3waEtpXvL1Xn4qu+BVitVtMh9lyThyhQ== @@ -15511,6 +15551,11 @@ vite@^4.4.12: optionalDependencies: fsevents "~2.3.2" +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + vscode-languageserver-types@^3.17.1: version "3.17.3" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" @@ -15534,17 +15579,14 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" -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== +wagmi@2.5.16: + version "2.5.16" + resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-2.5.16.tgz#42e4be44dd8e96deda10d953b91d13ee3a512758" + integrity sha512-nVSx65kGYcjvELJKoe+l1E78zHqtYwfg0nf2HuZkt1gVvqguo+hIQe534hSf1m+ivdtb0MPlwNCvj6K4QJ7FTw== 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.4.12" - abitype "0.8.7" - use-sync-external-store "^1.2.0" + "@wagmi/connectors" "4.1.22" + "@wagmi/core" "2.6.13" + use-sync-external-store "1.2.0" walker@^1.0.8: version "1.0.8" @@ -15733,7 +15775,7 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.12.0, ws@^8.5.0: +ws@8.12.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== @@ -15743,12 +15785,12 @@ ws@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: +ws@^7.3.1, ws@^7.5.1: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.11.0, ws@^8.9.0: +ws@^8.11.0, ws@^8.9.0, ws@~8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== @@ -15775,6 +15817,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + xorshift@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/xorshift/-/xorshift-1.2.0.tgz#30a4cdd8e9f8d09d959ed2a88c42a09c660e8148" @@ -15871,7 +15918,7 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" -yargs@^17.7.2: +yargs@^17.5.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -15904,9 +15951,9 @@ zod@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" - integrity sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg== +zustand@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.1.tgz#0cd3a3e4756f21811bd956418fdc686877e8b3b0" + integrity sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw== dependencies: use-sync-external-store "1.2.0"