diff --git a/.eslintrc.js b/.eslintrc.js index 20881d308f..cd540371c6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ const RESTRICTED_MODULES = { { name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' }, { name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' }, { name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' }, + { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, ], patterns: [ 'icons/*', diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000000..c9de8448b3 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,21 @@ +changelog: + categories: + - title: 🚀 New Features + labels: + - client feature + - feature + - title: 🐛 Bug Fixes + labels: + - bug + - title: ⚡ Performance Improvements + labels: + - performance + - title: ðŸ“Ķ Dependencies Updates + labels: + - dependencies + - title: ðŸšĻ Changes in ENV variables + labels: + - ENVs + - title: âœĻ Other Changes + labels: + - "*" \ No newline at end of file diff --git a/.github/workflows/copy-issues-labels.yml b/.github/workflows/copy-issues-labels.yml new file mode 100644 index 0000000000..e05b6e88ee --- /dev/null +++ b/.github/workflows/copy-issues-labels.yml @@ -0,0 +1,116 @@ +name: Copy issues labels to pull request + +on: + workflow_dispatch: + inputs: + pr_number: + description: Pull request number + required: true + type: string + issues: + description: JSON encoded list of issue ids + required: true + type: string + workflow_call: + inputs: + pr_number: + description: Pull request number + required: true + type: string + issues: + description: JSON encoded list of issue ids + required: true + type: string + +jobs: + run: + name: Run + runs-on: ubuntu-latest + steps: + - name: Find unique labels + id: find_unique_labels + uses: actions/github-script@v7 + env: + ISSUES: ${{ inputs.issues }} + with: + script: | + const issues = JSON.parse(process.env.ISSUES); + + const WHITE_LISTED_LABELS = [ + 'client feature', + 'feature', + + 'bug', + + 'dependencies', + 'performance', + + 'chore', + 'enhancement', + 'refactoring', + 'tech', + 'ENVs', + ] + + const labels = await Promise.all(issues.map(getIssueLabels)); + const uniqueLabels = uniqueStringArray(labels.flat().filter((label) => WHITE_LISTED_LABELS.includes(label))); + + if (uniqueLabels.length === 0) { + core.info('No labels found.\n'); + return []; + } + + core.info(`Found following labels: ${ uniqueLabels.join(', ') }.\n`); + return uniqueLabels; + + async function getIssueLabels(issue) { + core.info(`Obtaining labels list for the issue #${ issue }...`); + + try { + const response = await github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/labels', { + owner: 'blockscout', + repo: 'frontend', + issue_number: issue, + }); + return response.data.map(({ name }) => name); + } catch (error) { + core.error(`Failed to obtain labels for the issue #${ issue }: ${ error.message }`); + return []; + } + } + + function uniqueStringArray(array) { + return Array.from(new Set(array)); + } + + - name: Update pull request labels + id: update_pr_labels + uses: actions/github-script@v7 + env: + LABELS: ${{ steps.find_unique_labels.outputs.result }} + PR_NUMBER: ${{ inputs.pr_number }} + with: + script: | + const labels = JSON.parse(process.env.LABELS); + const prNumber = Number(process.env.PR_NUMBER); + + if (labels.length === 0) { + core.info('Nothing to update.\n'); + return; + } + + for (const label of labels) { + await addLabelToPr(prNumber, label); + } + core.info('Done.\n'); + + async function addLabelToPr(prNumber, label) { + console.log(`Adding label to the pull request #${ prNumber }...`); + + return await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/labels', { + owner: 'blockscout', + repo: 'frontend', + issue_number: prNumber, + labels: [ label ], + }); + } \ No newline at end of file diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index 087934c334..9e7f11dee9 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -4,6 +4,16 @@ on: push: branches: - main + paths-ignore: + - '.github/ISSUE_TEMPLATE/**' + - '.husky/**' + - '.vscode/**' + - 'docs/**' + - 'jest/**' + - 'mocks/**' + - 'playwright/**' + - 'stubs/**' + - 'tools/**' workflow_dispatch: concurrency: diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index c45cb9249d..05155cbd32 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -55,7 +55,10 @@ jobs: label_description: Tasks in pre-release right now secrets: inherit + # Temporary disable this step because it is broken + # There is an issue with building web3modal deps upload_source_maps: name: Upload source maps to Sentry + if: false uses: './.github/workflows/upload-source-maps.yml' secrets: inherit diff --git a/.github/workflows/project-management.yml b/.github/workflows/project-management.yml index ce53245267..1da733baeb 100644 --- a/.github/workflows/project-management.yml +++ b/.github/workflows/project-management.yml @@ -28,7 +28,7 @@ jobs: issues: "[${{ github.event.issue.number }}]" secrets: inherit - review_requested_issues: + pr_linked_issues: name: Get issues linked to PR runs-on: ubuntu-latest if: ${{ github.event.pull_request && github.event.action == 'review_requested' }} @@ -76,14 +76,24 @@ jobs: return issues; - review_requested_tasks: + issues_in_review: name: Update status for issues in review - needs: [ review_requested_issues ] - if: ${{ needs.review_requested_issues.outputs.issues }} + needs: [ pr_linked_issues ] + if: ${{ needs.pr_linked_issues.outputs.issues }} uses: './.github/workflows/update-project-cards.yml' secrets: inherit with: project_name: ${{ vars.PROJECT_NAME }} field_name: Status field_value: Review - issues: ${{ needs.review_requested_issues.outputs.issues }} + issues: ${{ needs.pr_linked_issues.outputs.issues }} + + copy_labels: + name: Copy issues labels to pull request + needs: [ pr_linked_issues ] + if: ${{ needs.pr_linked_issues.outputs.issues }} + uses: './.github/workflows/copy-issues-labels.yml' + secrets: inherit + with: + pr_number: ${{ github.event.pull_request.number }} + issues: ${{ needs.pr_linked_issues.outputs.issues }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb0c863799..df2c0433b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,8 +81,11 @@ jobs: with: platforms: linux/amd64,linux/arm64/v8 + # Temporary disable this step because it is broken + # There is an issue with building web3modal deps upload_source_maps: name: Upload source maps to Sentry + if: false needs: publish_image uses: './.github/workflows/upload-source-maps.yml' secrets: inherit diff --git a/.gitignore b/.gitignore index 721584d133..95523a63a2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ # next.js /.next/ /out/ -/public/assets/ -/public/envs.js +/public/assets/envs.js +/public/assets/configs /public/icons/sprite.svg /public/icons/README.md /analyze diff --git a/Dockerfile b/Dockerfile index bfe83d3a2a..83aa8c4b13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,11 +34,13 @@ RUN yarn --frozen-lockfile FROM node:20.11.0-alpine AS builder RUN apk add --no-cache --upgrade libc6-compat bash -# pass commit sha and git tag to the app image +# pass build args to env variables ARG GIT_COMMIT_SHA ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA ARG GIT_TAG ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG +ARG NEXT_OPEN_TELEMETRY_ENABLED +ENV NEXT_OPEN_TELEMETRY_ENABLED=$NEXT_OPEN_TELEMETRY_ENABLED ENV NODE_ENV production @@ -58,8 +60,8 @@ RUN ./collect_envs.sh ./docs/ENVS.md # ENV NEXT_TELEMETRY_DISABLED 1 # Build app for production -RUN yarn build RUN yarn svg:build-sprite +RUN yarn build ### FEATURE REPORTER diff --git a/configs/app/chain.ts b/configs/app/chain.ts index 32f5376aac..66d60e1779 100644 --- a/configs/app/chain.ts +++ b/configs/app/chain.ts @@ -15,6 +15,7 @@ const chain = Object.freeze({ secondaryCoin: { symbol: getEnvValue('NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL'), }, + tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', verificationType: getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') || 'mining', diff --git a/configs/app/features/deFiDropdown.ts b/configs/app/features/deFiDropdown.ts new file mode 100644 index 0000000000..62b8fdcd14 --- /dev/null +++ b/configs/app/features/deFiDropdown.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; +import type { DeFiDropdownItem } from 'types/client/deFiDropdown'; + +import { getEnvValue, parseEnvJson } from '../utils'; + +const items = parseEnvJson>(getEnvValue('NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS')) || []; + +const title = 'DeFi dropdown'; + +const config: Feature<{ items: Array }> = items.length > 0 ? + Object.freeze({ + title, + isEnabled: true, + items, + }) : + Object.freeze({ + title, + isEnabled: false, + }); + +export default config; diff --git a/configs/app/features/faultProofSystem.ts b/configs/app/features/faultProofSystem.ts new file mode 100644 index 0000000000..38a8f021db --- /dev/null +++ b/configs/app/features/faultProofSystem.ts @@ -0,0 +1,22 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import rollup from './rollup'; + +const title = 'Fault proof system'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (rollup.isEnabled && rollup.type === 'optimistic' && getEnvValue('NEXT_PUBLIC_FAULT_PROOF_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 3272c26741..b2ac272980 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -8,6 +8,8 @@ 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 deFiDropdown } from './deFiDropdown'; +export { default as faultProofSystem } from './faultProofSystem'; export { default as gasTracker } from './gasTracker'; export { default as googleAnalytics } from './googleAnalytics'; export { default as graphqlApiDocs } from './graphqlApiDocs'; @@ -15,7 +17,9 @@ export { default as growthBook } from './growthBook'; export { default as marketplace } from './marketplace'; export { default as metasuites } from './metasuites'; export { default as mixpanel } from './mixpanel'; +export { default as multichainButton } from './multichainButton'; export { default as nameService } from './nameService'; +export { default as publicTagsSubmission } from './publicTagsSubmission'; export { default as restApiDocs } from './restApiDocs'; export { default as rollup } from './rollup'; export { default as safe } from './safe'; @@ -23,7 +27,6 @@ export { default as sentry } from './sentry'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; export { default as suave } from './suave'; -export { default as swapButton } from './swapButton'; export { default as txInterpretation } from './txInterpretation'; export { default as userOps } from './userOps'; export { default as validators } from './validators'; diff --git a/configs/app/features/multichainButton.ts b/configs/app/features/multichainButton.ts new file mode 100644 index 0000000000..47b1433d05 --- /dev/null +++ b/configs/app/features/multichainButton.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; +import type { MultichainProviderConfig } from 'types/client/multichainProviderConfig'; + +import { getEnvValue, parseEnvJson } from '../utils'; +import marketplace from './marketplace'; + +const value = parseEnvJson(getEnvValue('NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG')); + +const title = 'Multichain balance'; + +const config: Feature<{name: string; logoUrl?: string; urlTemplate: string; dappId?: string }> = (() => { + if (value) { + return Object.freeze({ + title, + isEnabled: true, + name: value.name, + logoUrl: value.logo, + urlTemplate: value.url_template, + dappId: marketplace.isEnabled ? value.dapp_id : undefined, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/publicTagsSubmission.ts b/configs/app/features/publicTagsSubmission.ts new file mode 100644 index 0000000000..296d5e143a --- /dev/null +++ b/configs/app/features/publicTagsSubmission.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; + +import services from '../services'; +import { getEnvValue } from '../utils'; +import addressMetadata from './addressMetadata'; + +const apiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST'); + +const title = 'Public tag submission'; + +const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => { + if (services.reCaptcha.siteKey && addressMetadata.isEnabled && apiHost) { + return Object.freeze({ + title, + isEnabled: true, + api: { + endpoint: apiHost, + basePath: '', + }, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/swapButton.ts b/configs/app/features/swapButton.ts deleted file mode 100644 index 415a4af2c9..0000000000 --- a/configs/app/features/swapButton.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; -import marketplace from './marketplace'; - -const value = getEnvValue('NEXT_PUBLIC_SWAP_BUTTON_URL'); - -const title = 'Swap button'; - -function isValidUrl(string: string) { - try { - new URL(string); - return true; - } catch (error) { - return false; - } -} - -const config: Feature<{ dappId: string } | { url: string }> = (() => { - if (value) { - if (isValidUrl(value)) { - return Object.freeze({ - title, - isEnabled: true, - url: value, - }); - } else if (marketplace.isEnabled) { - return Object.freeze({ - title, - isEnabled: true, - dappId: value, - }); - } - } - - return Object.freeze({ - title, - isEnabled: false, - }); -})(); - -export default config; diff --git a/configs/app/ui.ts b/configs/app/ui.ts index 7f6832bf1f..2ef0f71a2a 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -2,6 +2,9 @@ import type { ContractCodeIde } from 'types/client/contract'; import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId } from 'types/client/navigation-items'; import type { ChainIndicatorId } from 'types/homepage'; import type { NetworkExplorer } from 'types/networks'; +import type { ColorThemeId } from 'types/settings'; + +import { COLOR_THEMES } from 'lib/settings/colorTheme'; import * as views from './ui/views'; import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from './utils'; @@ -21,6 +24,16 @@ const hiddenLinks = (() => { return result; })(); +const highlightedRoutes = (() => { + const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES')); + return Array.isArray(parsedValue) ? parsedValue : []; +})(); + +const defaultColorTheme = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_COLOR_THEME_DEFAULT') as ColorThemeId | undefined; + return COLOR_THEMES.find((theme) => theme.id === envValue); +})(); + // eslint-disable-next-line max-len const HOMEPAGE_PLATE_BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; @@ -35,6 +48,7 @@ const UI = Object.freeze({ dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK'), }, hiddenLinks, + highlightedRoutes, otherLinks: parseEnvJson>(getEnvValue('NEXT_PUBLIC_OTHER_LINKS')) || [], featuredNetworks: getExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS'), }, @@ -70,6 +84,9 @@ const UI = Object.freeze({ items: parseEnvJson>(getEnvValue('NEXT_PUBLIC_CONTRACT_CODE_IDES')) || [], }, hasContractAuditReports: getEnvValue('NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS') === 'true' ? true : false, + colorTheme: { + 'default': defaultColorTheme, + }, }); export default UI; diff --git a/configs/app/utils.ts b/configs/app/utils.ts index 5f24c8829f..97a9afd05d 100644 --- a/configs/app/utils.ts +++ b/configs/app/utils.ts @@ -41,7 +41,7 @@ export const buildExternalAssetFilePath = (name: string, value: string) => { const fileName = name.replace(/^NEXT_PUBLIC_/, '').replace(/_URL$/, '').toLowerCase(); const url = new URL(value); const fileExtension = url.pathname.match(regexp.FILE_EXTENSION)?.[1]; - return `/assets/${ fileName }.${ fileExtension }`; + return `/assets/configs/${ fileName }.${ fileExtension }`; } catch (error) { return; } diff --git a/configs/envs/.env.base b/configs/envs/.env.base index 4753aef51f..eafa448e36 100644 --- a/configs/envs/.env.base +++ b/configs/envs/.env.base @@ -56,7 +56,6 @@ NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'728301','width':'728','height':'90 NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'728301','width':'320','height':'100'} NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com -NEXT_PUBLIC_SWAP_BUTTON_URL=aerodrome NEXT_PUBLIC_MARKETPLACE_ENABLED=true 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 diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 2019083866..e277971bae 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -46,12 +46,17 @@ 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_METADATA_SERVICE_API_HOST=https://metadata.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_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] #meta NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true diff --git a/configs/envs/.env.eth_goerli b/configs/envs/.env.eth_goerli index 997d31c125..5bf4167f20 100644 --- a/configs/envs/.env.eth_goerli +++ b/configs/envs/.env.eth_goerli @@ -56,4 +56,4 @@ NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blocks NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout #meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true \ No newline at end of file +NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true diff --git a/configs/envs/.env.eth_sepolia b/configs/envs/.env.eth_sepolia index 8f3cb0e164..f2f78a62a8 100644 --- a/configs/envs/.env.eth_sepolia +++ b/configs/envs/.env.eth_sepolia @@ -18,14 +18,14 @@ NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io NEXT_PUBLIC_IS_TESTNET=true # api configuration -NEXT_PUBLIC_API_HOST=eth-sepolia.blockscout.com +NEXT_PUBLIC_API_HOST=eth-sepolia.k8s-dev.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ # ui config ## homepage NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(51, 53, 67, 1)' -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(165, 252, 122, 1)' +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(51,53,67,1) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(165,252,122,1) ## sidebar NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg @@ -33,6 +33,7 @@ NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/front NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/blocks','/apps'] ## footer NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/sepolia.json ##views @@ -47,7 +48,11 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 9322f0dbca..4afd0964c0 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -28,6 +28,7 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-logos/goerli.svg NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/goerli.svg +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps'] ## footer ##views 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'}] @@ -57,4 +58,3 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com -NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap diff --git a/configs/envs/.env.optimism b/configs/envs/.env.optimism index f5438e5fc8..2d638eba11 100644 --- a/configs/envs/.env.optimism +++ b/configs/envs/.env.optimism @@ -57,10 +57,9 @@ NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blocksc NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL -NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout # rollup 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.blockscout.com/ \ No newline at end of file +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ diff --git a/configs/envs/.env.optimism_sepolia b/configs/envs/.env.optimism_sepolia index 7bf13dc4a1..b7add2fe6c 100644 --- a/configs/envs/.env.optimism_sepolia +++ b/configs/envs/.env.optimism_sepolia @@ -53,7 +53,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 NEXT_PUBLIC_MARKETPLACE_ENABLED=false -NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true @@ -61,3 +60,4 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true 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-sepolia.blockscout.com/ +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index 223abf5f32..7840c51b42 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -51,5 +51,6 @@ NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx 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_METADATA_SERVICE_API_HOST=http://localhost:3007 NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx -NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx \ No newline at end of file +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx diff --git a/deploy/scripts/entrypoint.sh b/deploy/scripts/entrypoint.sh index 6f0f8c17b1..298303c7a0 100755 --- a/deploy/scripts/entrypoint.sh +++ b/deploy/scripts/entrypoint.sh @@ -36,7 +36,7 @@ export_envs_from_preset() { export_envs_from_preset # Download external assets -./download_assets.sh ./public/assets +./download_assets.sh ./public/assets/configs # Check run-time ENVs values ./validate_envs.sh diff --git a/deploy/scripts/favicon_generator.sh b/deploy/scripts/favicon_generator.sh index 4f153dcb0b..0cfc9f7f8a 100755 --- a/deploy/scripts/favicon_generator.sh +++ b/deploy/scripts/favicon_generator.sh @@ -10,7 +10,7 @@ if [ $? -ne 0 ]; then exit 1 else cd ../../../ - favicon_folder="./public/favicon/" + favicon_folder="./public/assets/favicon/" echo "âģ Replacing default favicons with freshly generated pack..." if [ -d "$favicon_folder" ]; then diff --git a/deploy/scripts/make_envs_script.sh b/deploy/scripts/make_envs_script.sh index 1124a1a77c..0634296c45 100755 --- a/deploy/scripts/make_envs_script.sh +++ b/deploy/scripts/make_envs_script.sh @@ -3,7 +3,7 @@ echo "🌀 Creating client script with ENV values..." # Define the output file name -output_file="${1:-./public/envs.js}" +output_file="${1:-./public/assets/envs.js}" touch $output_file; truncate -s 0 $output_file; diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 0db98f496c..e361270b44 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -12,9 +12,11 @@ import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; 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 type { DeFiDropdownItem } from '../../../types/client/deFiDropdown'; import { GAS_UNITS } from '../../../types/client/gasTracker'; import type { GasUnit } from '../../../types/client/gasTracker'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace'; +import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig'; 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'; @@ -28,6 +30,7 @@ import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import { CHAIN_INDICATOR_IDS } from '../../../types/homepage'; import type { ChainIndicatorId } from '../../../types/homepage'; import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks'; +import { COLOR_THEME_IDS } from '../../../types/settings'; import type { AddressViewId } from '../../../types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; import { BLOCK_FIELDS_IDS } from '../../../types/views/block'; @@ -38,6 +41,7 @@ import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx import { replaceQuotes } from '../../../configs/app/utils'; import * as regexp from '../../../lib/regexp'; +import type { IconName } from '../../../ui/shared/IconSvg'; const protocols = [ 'http', 'https' ]; @@ -351,21 +355,6 @@ const accountSchema = yup }), }); -const adminServiceSchema = yup - .object() - .shape({ - NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup - .string() - .when([ 'NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', 'NEXT_PUBLIC_MARKETPLACE_ENABLED' ], { - is: (value1: boolean, value2: boolean) => value1 || value2, - then: (schema) => schema.test(urlTest), - otherwise: (schema) => schema.max( - -1, - 'NEXT_PUBLIC_ADMIN_SERVICE_API_HOST cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED or NEXT_PUBLIC_MARKETPLACE_ENABLED is not set to "true"', - ), - }), - }); - const featuredNetworkSchema: yup.ObjectSchema = yup .object() .shape({ @@ -463,6 +452,17 @@ const bridgedTokensSchema = yup }), }); +const deFiDropdownItemSchema: yup.ObjectSchema = yup + .object({ + text: yup.string().required(), + icon: yup.string().required(), + dappId: yup.string(), + url: yup.string().test(urlTest), + }) + .test('oneOfRequired', 'NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: Either dappId or url is required', function(value) { + return Boolean(value.dappId) || Boolean(value.url); + }) as yup.ObjectSchema; + const schema = yup .object() .noUnknown(true, (params) => { @@ -492,6 +492,7 @@ const schema = yup NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(), NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string().oneOf([ 'validation', 'mining' ]), + NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(), NEXT_PUBLIC_IS_TESTNET: yup.boolean(), // 3. API configuration @@ -527,6 +528,11 @@ const schema = yup .transform(replaceQuotes) .json() .of(yup.string().oneOf(NAVIGATION_LINK_IDS)), + NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string()), NEXT_PUBLIC_NETWORK_LOGO: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_ICON: yup.string().test(urlTest), @@ -582,6 +588,7 @@ const schema = yup NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(), NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), + NEXT_PUBLIC_COLOR_THEME_DEFAULT: yup.string().oneOf(COLOR_THEME_IDS), // 5. Features configuration NEXT_PUBLIC_API_SPEC_URL: yup.string().test(urlTest), @@ -592,6 +599,7 @@ const schema = yup NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_NAME_SERVICE_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_METADATA_SERVICE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup.string().matches(regexp.HEX_REGEXP), NEXT_PUBLIC_WEB3_WALLETS: yup .mixed() @@ -617,11 +625,38 @@ const schema = yup NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(), NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(), NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(), - NEXT_PUBLIC_SWAP_BUTTON_URL: yup.string(), + NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG, it should have name and url template', (data) => { + const isUndefined = data === undefined; + const valueSchema = yup.object().transform(replaceQuotes).json().shape({ + name: yup.string().required(), + url_template: yup.string().required(), + logo: yup.string(), + dapp_id: yup.string(), + }); + + return isUndefined || valueSchema.isValidSync(data); + }), 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(), + NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: yup + .array() + .transform(replaceQuotes) + .json() + .of(deFiDropdownItemSchema), + NEXT_PUBLIC_FAULT_PROOF_ENABLED: yup.boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'optimistic', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_FAULT_PROOF_ENABLED can only be used with NEXT_PUBLIC_ROLLUP_TYPE=optimistic', + value => value === undefined, + ), + }), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), @@ -639,7 +674,6 @@ const schema = yup .concat(rollupSchema) .concat(beaconChainSchema) .concat(bridgedTokensSchema) - .concat(sentrySchema) - .concat(adminServiceSchema); + .concat(sentrySchema); export default schema; diff --git a/deploy/tools/envs-validator/test.sh b/deploy/tools/envs-validator/test.sh index 7e29716658..46d3ea3fca 100755 --- a/deploy/tools/envs-validator/test.sh +++ b/deploy/tools/envs-validator/test.sh @@ -9,7 +9,7 @@ export NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0) ../../scripts/collect_envs.sh ../../../docs/ENVS.md # Copy test assets -mkdir -p "./public/assets" +mkdir -p "./public/assets/configs" cp -r ${test_folder}/assets ./public/ # Build validator script diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 4ed2fa938a..f33be478e7 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -21,10 +21,12 @@ NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS=[{'id':'1','title':'Ethereum','short_title':'ETH','base_url':'https://example.com'}] NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES=[{'type':'omni','title':'OmniBridge','short_title':'OMNI'}] +NEXT_PUBLIC_COLOR_THEME_DEFAULT=dim 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_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps'] NEXT_PUBLIC_FOOTER_LINKS=https://example.com NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false @@ -71,5 +73,6 @@ 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 NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','url':'https://example.com'}] +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} diff --git a/deploy/tools/envs-validator/test/.env.rollup b/deploy/tools/envs-validator/test/.env.rollup index 1cb3e1af88..e7cacfb086 100644 --- a/deploy/tools/envs-validator/test/.env.rollup +++ b/deploy/tools/envs-validator/test/.env.rollup @@ -1,3 +1,4 @@ NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com -NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com \ No newline at end of file +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/assets/featured_networks.json b/deploy/tools/envs-validator/test/assets/configs/featured_networks.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/featured_networks.json rename to deploy/tools/envs-validator/test/assets/configs/featured_networks.json diff --git a/deploy/tools/envs-validator/test/assets/footer_links.json b/deploy/tools/envs-validator/test/assets/configs/footer_links.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/footer_links.json rename to deploy/tools/envs-validator/test/assets/configs/footer_links.json diff --git a/deploy/tools/envs-validator/test/assets/marketplace_categories.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_categories.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/marketplace_categories.json rename to deploy/tools/envs-validator/test/assets/configs/marketplace_categories.json diff --git a/deploy/tools/envs-validator/test/assets/marketplace_config.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_config.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/marketplace_config.json rename to deploy/tools/envs-validator/test/assets/configs/marketplace_config.json diff --git a/deploy/tools/envs-validator/test/assets/marketplace_security_reports.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_security_reports.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/marketplace_security_reports.json rename to deploy/tools/envs-validator/test/assets/configs/marketplace_security_reports.json diff --git a/deploy/values/l2-optimism-goerli/values.yaml b/deploy/values/l2-optimism-goerli/values.yaml index afd8dcc1a0..f91668e67f 100644 --- a/deploy/values/l2-optimism-goerli/values.yaml +++ b/deploy/values/l2-optimism-goerli/values.yaml @@ -197,7 +197,6 @@ frontend: NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth-goerli.blockscout.com/ NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 - NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap envFromSecret: NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID diff --git a/deploy/values/main/values.yaml b/deploy/values/main/values.yaml index 8504a27e31..a3d3ea3fdd 100644 --- a/deploy/values/main/values.yaml +++ b/deploy/values/main/values.yaml @@ -165,7 +165,6 @@ frontend: NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" #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_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-sepolia.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" - NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap envFromSecret: NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI diff --git a/deploy/values/review-l2/values.yaml.gotmpl b/deploy/values/review-l2/values.yaml.gotmpl index eea25de5e8..10eecb0000 100644 --- a/deploy/values/review-l2/values.yaml.gotmpl +++ b/deploy/values/review-l2/values.yaml.gotmpl @@ -74,7 +74,6 @@ frontend: NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_USE_NEXT_JS_PROXY: true - NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap envFromSecret: NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?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-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index e738ae3e63..74315a5a7b 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -86,7 +86,6 @@ frontend: 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: blockscout - 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 diff --git a/docs/DEPRECATED_ENVS.md b/docs/DEPRECATED_ENVS.md index 00c6ef1629..a8e17450be 100644 --- a/docs/DEPRECATED_ENVS.md +++ b/docs/DEPRECATED_ENVS.md @@ -7,4 +7,5 @@ | NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | | NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L1_BASE_URL | | NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | v1.25.0 | Replaced by NEXT_PUBLIC_GAS_TRACKER_ENABLED | -| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | \ No newline at end of file +| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | +| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | diff --git a/docs/ENVS.md b/docs/ENVS.md index 1972e42cdf..9fd8e3eee7 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -57,6 +57,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Sentry error monitoring](ENVS.md#sentry-error-monitoring) - [OpenTelemetry](ENVS.md#opentelemetry) - [Swap button](ENVS.md#swap-button) + - [Multichain balance button](ENVS.md#multichain-button) - [3rd party services configuration](ENVS.md#external-services-configuration)   @@ -88,6 +89,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will | NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | | NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | | NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` | +| NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | | NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` |   @@ -128,6 +130,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will | NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) which contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` | | NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | | NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS | `Array` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | +| NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES | `Array` | List of menu item routes that should have a lightning label | - | - | `['/accounts']` | #### Featured network configuration properties @@ -275,6 +278,7 @@ Settings for meta tags, OG tags and SEO | NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS | `boolean` | Set to `true` to hide indexing alert in the page header about indexing chain's blocks | - | `false` | `true` | | NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS | `boolean` | Set to `true` to hide indexing alert in the page footer about indexing block's internal transactions | - | `false` | `true` | | NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE | `string` | Used for displaying custom announcements or alerts in the header of the site. Could be a regular string or a HTML code. | - | - | `Hello world! ðŸĪŠ` | +| NEXT_PUBLIC_COLOR_THEME_DEFAULT | `'light' \| 'dim' \| 'midnight' \| 'dark'` | Preferred color theme of the app | - | - | `midnight` | #### Network explorer configuration properties @@ -395,6 +399,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | 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 | Required only for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | +| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (OP stack only) | - | - | `true` |   @@ -566,6 +571,17 @@ This feature allows name tags and other public tags for addresses.   +### Public tag submission + +This feature allows you to submit an application with a public address tag. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_METADATA_SERVICE_API_HOST | `string` | Metadata Service API endpoint url | Required | - | `https://metadata.services.blockscout.com` | +| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url | Required | - | `https://admin-rs.services.blockscout.com` | + +  + ### 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. @@ -660,21 +676,42 @@ The feature enables the Validators page which provides detailed information abou ### OpenTelemetry -OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). +OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). Note that this Next.js feature is currently experimental. The Docker image should be built with the `NEXT_OPEN_TELEMETRY_ENABLED=true` argument to enable it. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| OTEL_SDK_ENABLED | `boolean` | Run-time flag to enable the feature | Required | `false` | `true` | + +  + +### DeFi dropdown + +If the feature is enabled, a single button or a dropdown (if more than 1 item is provided) will be displayed at the top of the explorer page, which will take a user to the specified application in the marketplace or to an external site. | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| OTEL_SDK_ENABLED | `boolean` | Flag to enable the feature | Required | `false` | `true` | +| NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon: string; dappId?: string, url?: string }]` | An array of dropdown items containing the button text, icon name and dappId in DAppscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` |   -### Swap button +### Multichain balance button + +If the feature is enabled, a Multichain balance button will be displayed on the address page, which will take you to the portfolio application in the marketplace or to an external site. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG | `{ name: string; url_template: string; dapp_id?: string; logo?: string }` | Multichain portfolio application config See [below](#multichain-button-configuration-properties) | - | - | `{ name: 'zerion', url_template: 'https://app.zerion.io/{address}/overview', logo: 'https://example.com/icon.svg'` | + +  -If the feature is enabled, a Swap button will be displayed at the top of the explorer page, which will take you to the specified application in the marketplace or to an external site. +#### Multichain button configuration properties | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | +| name | `string` | Multichain portfolio application name | Required | - | `zerion` | +| url_template | `string` | Url template to the portfolio. Should be a template with `{address}` variable | Required | - | `https://app.zerion.io/{address}/overview` | +| dapp_id | `string` | Set for open a Blockscout dapp page with the portfolio instead of opening external app page | - | - | `zerion` | +| logo | `string` | Multichain portfolio application logo (.svg) url | - | - | `https://example.com/icon.svg` |   diff --git a/icons/verified_token.svg b/icons/certified.svg similarity index 100% rename from icons/verified_token.svg rename to icons/certified.svg diff --git a/icons/games.svg b/icons/games.svg new file mode 100644 index 0000000000..39c01ca56d --- /dev/null +++ b/icons/games.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/icons/lightning_navbar.svg b/icons/lightning_navbar.svg new file mode 100644 index 0000000000..9587a9c7a2 --- /dev/null +++ b/icons/lightning_navbar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/payment_link.svg b/icons/payment_link.svg new file mode 100644 index 0000000000..f97128fff6 --- /dev/null +++ b/icons/payment_link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/swap.svg b/icons/swap.svg index 63d915c99b..c1566be5fc 100644 --- a/icons/swap.svg +++ b/icons/swap.svg @@ -1,3 +1,3 @@ - - + + diff --git a/instrumentation.node.ts b/instrumentation.node.ts index bbb183dd00..5cae612a92 100644 --- a/instrumentation.node.ts +++ b/instrumentation.node.ts @@ -46,9 +46,7 @@ const sdk = new NodeSDK({ url.pathname.startsWith('/_next/static/') || url.pathname.startsWith('/_next/data/') || url.pathname.startsWith('/assets/') || - url.pathname.startsWith('/static/') || - url.pathname.startsWith('/favicon/') || - url.pathname.startsWith('/envs.js') + url.pathname.startsWith('/static/') ) { return true; } diff --git a/jest/lib.tsx b/jest/lib.tsx index 823e4b1f6e..deafc637c6 100644 --- a/jest/lib.tsx +++ b/jest/lib.tsx @@ -16,7 +16,7 @@ const PAGE_PROPS = { cookies: '', referrer: '', query: {}, - adBannerProvider: undefined, + adBannerProvider: null, apiData: null, }; diff --git a/lib/address/parseMetaPayload.ts b/lib/address/parseMetaPayload.ts index ad5e8d401a..e8b067b148 100644 --- a/lib/address/parseMetaPayload.ts +++ b/lib/address/parseMetaPayload.ts @@ -16,11 +16,17 @@ export default function parseMetaPayload(meta: AddressMetadataTag['meta']): Addr const stringFields: Array = [ 'textColor', 'bgColor', + 'tagIcon', 'tagUrl', 'tooltipIcon', 'tooltipTitle', 'tooltipDescription', 'tooltipUrl', + 'appID', + 'appMarketplaceURL', + 'appLogoURL', + 'appActionButtonText', + 'warpcastHandle', ]; for (const stringField of stringFields) { diff --git a/lib/address/useAddressMetadataInfoQuery.ts b/lib/address/useAddressMetadataInfoQuery.ts index 2268e8636b..1d2f11043c 100644 --- a/lib/address/useAddressMetadataInfoQuery.ts +++ b/lib/address/useAddressMetadataInfoQuery.ts @@ -1,47 +1,35 @@ -import { useQuery } from '@tanstack/react-query'; - -import type { AddressMetadataInfo } from 'types/api/addressMetadata'; import type { AddressMetadataInfoFormatted, AddressMetadataTagFormatted } from 'types/client/addressMetadata'; import config from 'configs/app'; -import useApiFetch from 'lib/api/useApiFetch'; -import { getResourceKey } from 'lib/api/useApiQuery'; +import useApiQuery from 'lib/api/useApiQuery'; import parseMetaPayload from './parseMetaPayload'; export default function useAddressMetadataInfoQuery(addresses: Array) { - const apiFetch = useApiFetch(); - - const queryParams = { - addresses, - chainId: config.chain.id, - tagsLimit: '20', - }; const resource = 'address_metadata_info'; - // TODO @tom2drum: Improve the typing here - // since we are formatting the API data in the select function here - // we cannot use the useApiQuery hook because of its current typing - // enhance useApiQuery so it can accept an API data and the formatted data types - return useQuery({ - queryKey: getResourceKey(resource, { queryParams }), - queryFn: async() => { - return apiFetch(resource, { queryParams }) as Promise; + return useApiQuery(resource, { + queryParams: { + addresses, + chainId: config.chain.id, + tagsLimit: '20', }, - enabled: addresses.length > 0 && config.features.addressMetadata.isEnabled, - select: (data) => { - const addresses = Object.entries(data.addresses) - .map(([ address, { tags, reputation } ]) => { - const formattedTags: Array = tags.map((tag) => ({ ...tag, meta: parseMetaPayload(tag.meta) })); - return [ address.toLowerCase(), { tags: formattedTags, reputation } ] as const; - }) - .reduce((result, item) => { - result[item[0]] = item[1]; - return result; - }, {} as AddressMetadataInfoFormatted['addresses']); - - return { addresses }; + queryOptions: { + enabled: addresses.length > 0 && config.features.addressMetadata.isEnabled, + select: (data) => { + const addresses = Object.entries(data.addresses) + .map(([ address, { tags, reputation } ]) => { + const formattedTags: Array = tags.map((tag) => ({ ...tag, meta: parseMetaPayload(tag.meta) })); + return [ address.toLowerCase(), { tags: formattedTags, reputation } ] as const; + }) + .reduce((result, item) => { + result[item[0]] = item[1]; + return result; + }, {} as AddressMetadataInfoFormatted['addresses']); + + return { addresses }; + }, }, }); } diff --git a/lib/api/resources.ts b/lib/api/resources.ts index f0a359c5c2..26686f287c 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -2,7 +2,6 @@ import { getFeaturePayload } from 'configs/app/features/types'; import type { UserInfo, CustomAbis, - PublicTags, ApiKeys, VerifiedAddressResponse, TokenInfoApplicationConfig, @@ -32,7 +31,7 @@ import type { AddressCoinBalanceHistoryChartOld, } from 'types/api/address'; import type { AddressesResponse } from 'types/api/addresses'; -import type { AddressMetadataInfo } from 'types/api/addressMetadata'; +import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; import type { TxBlobs, Blob } from 'types/api/blobs'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block'; import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts'; @@ -65,6 +64,7 @@ import type { OptimisticL2OutputRootsResponse, OptimisticL2TxnBatchesResponse, OptimisticL2WithdrawalsResponse, + OptimisticL2DisputeGamesResponse, } from 'types/api/optimisticL2'; import type { RawTracesResponse } from 'types/api/rawTrace'; import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search'; @@ -147,10 +147,6 @@ export const RESOURCES = { pathParams: [ 'id' as const ], filterFields: [ ], }, - public_tags: { - path: '/api/account/v2/user/public_tags/:id?', - pathParams: [ 'id' as const ], - }, private_tags_address: { path: '/api/account/v2/user/tags/address/:id?', pathParams: [ 'id' as const ], @@ -245,7 +241,7 @@ export const RESOURCES = { filterFields: [ 'name' as const, 'only_active' as const ], }, - // METADATA SERVICE + // METADATA SERVICE & PUBLIC TAGS address_metadata_info: { path: '/api/v1/metadata', endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, @@ -256,6 +252,17 @@ export const RESOURCES = { endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath, }, + address_metadata_tag_types: { + path: '/api/v1/public-tag-types', + endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, + basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath, + }, + public_tag_application: { + path: '/api/v1/chains/:chainId/metadata-submissions/tag', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.publicTagsSubmission)?.api.endpoint, + basePath: getFeaturePayload(config.features.publicTagsSubmission)?.api.basePath, + }, // VISUALIZATION visualize_sol2uml: { @@ -644,6 +651,15 @@ export const RESOURCES = { path: '/api/v2/optimism/txn-batches/count', }, + optimistic_l2_dispute_games: { + path: '/api/v2/optimism/games', + filterFields: [], + }, + + optimistic_l2_dispute_games_count: { + path: '/api/v2/optimism/games/count', + }, + // zkEvm L2 zkevm_l2_deposits: { path: '/api/v2/zkevm/deposits', @@ -847,6 +863,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'token_instance_transfers' | 'token_instance_holders' | 'verified_contracts' | 'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' | +'optimistic_l2_dispute_games' | 'shibarium_deposits' | 'shibarium_withdrawals' | 'zkevm_l2_deposits' | 'zkevm_l2_withdrawals' | 'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' | 'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' | @@ -863,7 +880,6 @@ export type PaginatedResponse = ResourcePayload export type ResourcePayloadA = Q extends 'user_info' ? UserInfo : Q extends 'custom_abi' ? CustomAbis : -Q extends 'public_tags' ? PublicTags : Q extends 'private_tags_address' ? AddressTagsResponse : Q extends 'private_tags_tx' ? TransactionTagsResponse : Q extends 'api_keys' ? ApiKeys : @@ -950,12 +966,12 @@ Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse : Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse : Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse : +Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse : Q extends 'optimistic_l2_output_roots_count' ? number : Q extends 'optimistic_l2_withdrawals_count' ? number : Q extends 'optimistic_l2_deposits_count' ? number : Q extends 'optimistic_l2_txn_batches_count' ? number : -Q extends 'config_backend_version' ? BackendVersionConfig : -Q extends 'address_metadata_info' ? AddressMetadataInfo : +Q extends 'optimistic_l2_dispute_games_count' ? number : never; // !!! IMPORTANT !!! // See comment above @@ -963,6 +979,9 @@ never; /* eslint-disable @typescript-eslint/indent */ export type ResourcePayloadB = +Q extends 'config_backend_version' ? BackendVersionConfig : +Q extends 'address_metadata_info' ? AddressMetadataInfo : +Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse : Q extends 'blob' ? Blob : Q extends 'marketplace_dapps' ? Array : Q extends 'marketplace_dapp' ? MarketplaceAppOverview : diff --git a/lib/api/useApiQuery.tsx b/lib/api/useApiQuery.tsx index 0d28e70c54..91b2ab6ae5 100644 --- a/lib/api/useApiQuery.tsx +++ b/lib/api/useApiQuery.tsx @@ -5,8 +5,8 @@ import type { ResourceError, ResourceName, ResourcePayload } from './resources'; import type { Params as ApiFetchParams } from './useApiFetch'; import useApiFetch from './useApiFetch'; -export interface Params extends ApiFetchParams { - queryOptions?: Omit, ResourceError, ResourcePayload>, 'queryKey' | 'queryFn'>; +export interface Params> extends ApiFetchParams { + queryOptions?: Omit, ResourceError, D>, 'queryKey' | 'queryFn'>; } export function getResourceKey(resource: R, { pathParams, queryParams }: Params = {}) { @@ -17,13 +17,13 @@ export function getResourceKey(resource: R, { pathParams return [ resource ]; } -export default function useApiQuery( +export default function useApiQuery>( resource: R, - { queryOptions, pathParams, queryParams, fetchParams }: Params = {}, + { queryOptions, pathParams, queryParams, fetchParams }: Params = {}, ) { const apiFetch = useApiFetch(); - return useQuery, ResourceError, ResourcePayload>({ + return useQuery, ResourceError, D>({ // eslint-disable-next-line @tanstack/query/exhaustive-deps queryKey: getResourceKey(resource, { pathParams, queryParams }), queryFn: async() => { diff --git a/lib/contexts/app.tsx b/lib/contexts/app.tsx index 256bf0bf56..dbeceace4d 100644 --- a/lib/contexts/app.tsx +++ b/lib/contexts/app.tsx @@ -12,7 +12,7 @@ const AppContext = createContext({ cookies: '', referrer: '', query: {}, - adBannerProvider: undefined, + adBannerProvider: null, apiData: null, }); diff --git a/lib/contexts/chakra.tsx b/lib/contexts/chakra.tsx index 49aa0971f2..d009d3b5f0 100644 --- a/lib/contexts/chakra.tsx +++ b/lib/contexts/chakra.tsx @@ -6,11 +6,13 @@ import { import type { ChakraProviderProps } from '@chakra-ui/react'; import React from 'react'; +import theme from 'theme'; + interface Props extends ChakraProviderProps { cookies?: string; } -export function ChakraProvider({ cookies, theme, children }: Props) { +export function ChakraProvider({ cookies, children }: Props) { const colorModeManager = typeof cookies === 'string' ? cookieStorageManagerSSR(typeof document !== 'undefined' ? document.cookie : cookies) : diff --git a/lib/growthbook/init.ts b/lib/growthbook/init.ts index d98b2b94b7..4aef06e705 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; + action_button_exp: boolean; } export const growthBook = (() => { diff --git a/lib/hooks/useAdblockDetect.tsx b/lib/hooks/useAdblockDetect.tsx index 415b150f07..c34442d7b4 100644 --- a/lib/hooks/useAdblockDetect.tsx +++ b/lib/hooks/useAdblockDetect.tsx @@ -34,9 +34,13 @@ export default function useAdblockDetect() { method: 'HEAD', mode: 'no-cors', cache: 'no-store', - }).catch(() => { - cookies.set(cookies.NAMES.ADBLOCK_DETECTED, 'true', { expires: 1 }); - }); + }) + .then(() => { + cookies.set(cookies.NAMES.ADBLOCK_DETECTED, 'false', { expires: 1 }); + }) + .catch(() => { + cookies.set(cookies.NAMES.ADBLOCK_DETECTED, 'true', { expires: 1 }); + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index bc11f3e5f4..5cfe16094a 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -96,6 +96,12 @@ export default function useNavItems(): ReturnType { icon: 'output_roots', isActive: pathname === '/output-roots', }; + const rollupDisputeGames = config.features.faultProofSystem.isEnabled ? { + text: 'Dispute games', + nextRoute: { pathname: '/dispute-games' as const }, + icon: 'games', + isActive: pathname === '/dispute-games', + } : null; const rollupFeature = config.features.rollup; @@ -109,6 +115,7 @@ export default function useNavItems(): ReturnType { [ blocks, rollupTxnBatches, + rollupDisputeGames, rollupFeature.type === 'optimistic' ? rollupOutputRoots : undefined, ].filter(Boolean), [ @@ -237,6 +244,11 @@ export default function useNavItems(): ReturnType { nextRoute: { pathname: '/gas-tracker' as const }, isActive: pathname.startsWith('/gas-tracker'), }, + config.features.publicTagsSubmission.isEnabled && { + text: 'Submit public tag', + nextRoute: { pathname: '/public-tags/submit' as const }, + isActive: pathname.startsWith('/public-tags/submit'), + }, ...config.UI.sidebar.otherLinks, ].filter(Boolean), }, @@ -255,12 +267,6 @@ export default function useNavItems(): ReturnType { icon: 'privattags', isActive: pathname === '/account/tag-address', }, - { - text: 'Public tags', - nextRoute: { pathname: '/account/public-tags-request' as const }, - icon: 'publictags', - isActive: pathname === '/account/public-tags-request', - }, { text: 'API keys', nextRoute: { pathname: '/account/api-key' as const }, diff --git a/lib/metadata/__snapshots__/generate.test.ts.snap b/lib/metadata/__snapshots__/generate.test.ts.snap index de88a039ef..a664df8d20 100644 --- a/lib/metadata/__snapshots__/generate.test.ts.snap +++ b/lib/metadata/__snapshots__/generate.test.ts.snap @@ -2,6 +2,7 @@ exports[`generates correct metadata for: dynamic route 1`] = ` { + "canonical": undefined, "description": "View transaction 0x12345 on Blockscout (Blockscout) Explorer", "opengraph": { "description": "", @@ -14,6 +15,7 @@ exports[`generates correct metadata for: dynamic route 1`] = ` exports[`generates correct metadata for: dynamic route with API data 1`] = ` { + "canonical": undefined, "description": "0x12345, balances and analytics on the Blockscout (Blockscout) Explorer", "opengraph": { "description": "", @@ -26,12 +28,13 @@ exports[`generates correct metadata for: dynamic route with API data 1`] = ` exports[`generates correct metadata for: static route 1`] = ` { + "canonical": "http://localhost:3000/txs", "description": "Blockscout is the #1 open-source blockchain explorer available today. 100+ chains and counting rely on Blockscout data availability, APIs, and ecosystem tools to support their networks.", "opengraph": { "description": "", "imageUrl": "http://localhost:3000/static/og_placeholder.png", - "title": "Blockscout blocks | Blockscout", + "title": "Blockscout transactions - Blockscout explorer | Blockscout", }, - "title": "Blockscout blocks | Blockscout", + "title": "Blockscout transactions - Blockscout explorer | Blockscout", } `; diff --git a/lib/metadata/generate.test.ts b/lib/metadata/generate.test.ts index b9cbe2f806..f2cd05dcc1 100644 --- a/lib/metadata/generate.test.ts +++ b/lib/metadata/generate.test.ts @@ -17,9 +17,9 @@ const TEST_CASES = [ { title: 'static route', route: { - pathname: '/blocks', + pathname: '/txs', }, - } as TestCase<'/blocks'>, + } as TestCase<'/txs'>, { title: 'dynamic route', route: { diff --git a/lib/metadata/generate.ts b/lib/metadata/generate.ts index 3c16903583..9282da7fe7 100644 --- a/lib/metadata/generate.ts +++ b/lib/metadata/generate.ts @@ -7,6 +7,7 @@ import config from 'configs/app'; import getNetworkTitle from 'lib/networks/getNetworkTitle'; import compileValue from './compileValue'; +import getCanonicalUrl from './getCanonicalUrl'; import getPageOgType from './getPageOgType'; import * as templates from './templates'; @@ -18,8 +19,7 @@ export default function generate(route: Rout network_title: getNetworkTitle(), }; - const compiledTitle = compileValue(templates.title.make(route.pathname, Boolean(apiData)), params); - const title = compiledTitle ? compiledTitle + (config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : '') : ''; + const title = compileValue(templates.title.make(route.pathname, Boolean(apiData)), params); const description = compileValue(templates.description.make(route.pathname), params); const pageOgType = getPageOgType(route.pathname); @@ -32,5 +32,6 @@ export default function generate(route: Rout description: pageOgType !== 'Regular page' ? config.meta.og.description : '', imageUrl: pageOgType !== 'Regular page' ? config.meta.og.imageUrl : '', }, + canonical: getCanonicalUrl(route.pathname), }; } diff --git a/lib/metadata/getCanonicalUrl.ts b/lib/metadata/getCanonicalUrl.ts new file mode 100644 index 0000000000..2a868419a8 --- /dev/null +++ b/lib/metadata/getCanonicalUrl.ts @@ -0,0 +1,24 @@ +import type { Route } from 'nextjs-routes'; + +import config from 'configs/app'; + +const CANONICAL_ROUTES: Array = [ + '/', + '/txs', + '/ops', + '/verified-contracts', + '/name-domains', + '/withdrawals', + '/tokens', + '/stats', + '/api-docs', + '/graphiql', + '/gas-tracker', + '/apps', +]; + +export default function getCanonicalUrl(pathname: Route['pathname']) { + if (CANONICAL_ROUTES.includes(pathname)) { + return config.app.baseUrl + pathname; + } +} diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index a195848744..66319f429e 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -27,14 +27,15 @@ const OG_TYPE_DICT: Record = { '/account/watchlist': 'Regular page', '/account/api-key': 'Regular page', '/account/custom-abi': 'Regular page', - '/account/public-tags-request': 'Regular page', '/account/tag-address': 'Regular page', '/account/verified-addresses': 'Root page', + '/public-tags/submit': 'Regular page', '/withdrawals': 'Root page', '/visualize/sol2uml': 'Regular page', '/csv-export': 'Regular page', '/deposits': 'Root page', '/output-roots': 'Root page', + '/dispute-games': 'Root page', '/batches': 'Root page', '/batches/[number]': 'Regular page', '/blobs/[hash]': 'Regular page', diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index dbaaf5e0fc..08acaaf1c8 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import type { Route } from 'nextjs-routes'; // equal og:description @@ -30,14 +31,15 @@ const TEMPLATE_MAP: Record = { '/account/watchlist': DEFAULT_TEMPLATE, '/account/api-key': DEFAULT_TEMPLATE, '/account/custom-abi': DEFAULT_TEMPLATE, - '/account/public-tags-request': DEFAULT_TEMPLATE, '/account/tag-address': DEFAULT_TEMPLATE, '/account/verified-addresses': DEFAULT_TEMPLATE, + '/public-tags/submit': 'Propose a new public tag for your address, contract or set of contracts for your dApp. Our team will review and approve your submission. Public tags are incredible tool which helps users identify contracts and addresses.', '/withdrawals': DEFAULT_TEMPLATE, '/visualize/sol2uml': DEFAULT_TEMPLATE, '/csv-export': DEFAULT_TEMPLATE, '/deposits': DEFAULT_TEMPLATE, '/output-roots': DEFAULT_TEMPLATE, + '/dispute-games': DEFAULT_TEMPLATE, '/batches': DEFAULT_TEMPLATE, '/batches/[number]': DEFAULT_TEMPLATE, '/blobs/[hash]': DEFAULT_TEMPLATE, diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index c82feb2c28..b004af7f7d 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -1,70 +1,74 @@ import type { Route } from 'nextjs-routes'; +import config from 'configs/app'; + const TEMPLATE_MAP: Record = { - '/': 'blockchain explorer', - '/txs': 'transactions', - '/txs/kettle/[hash]': 'kettle %hash% transactions', - '/tx/[hash]': 'transaction %hash%', - '/blocks': 'blocks', - '/block/[height_or_hash]': 'block %height_or_hash%', - '/accounts': 'top accounts', - '/address/[hash]': 'address details for %hash%', - '/verified-contracts': 'verified contracts', - '/contract-verification': 'verify contract', - '/address/[hash]/contract-verification': 'contract verification for %hash%', - '/tokens': 'tokens', - '/token/[hash]': 'token details', - '/token/[hash]/instance/[id]': 'NFT instance', - '/apps': 'apps marketplace', - '/apps/[id]': 'marketplace app', - '/stats': 'statistics', - '/api-docs': 'REST API', - '/graphiql': 'GraphQL', - '/search-results': 'search result for %q%', - '/auth/profile': '- my profile', - '/account/watchlist': '- watchlist', - '/account/api-key': '- API keys', - '/account/custom-abi': '- custom ABI', - '/account/public-tags-request': '- public tag requests', - '/account/tag-address': '- private tags', - '/account/verified-addresses': '- my verified addresses', - '/withdrawals': 'withdrawals', - '/visualize/sol2uml': 'Solidity UML diagram', - '/csv-export': 'export data to CSV', - '/deposits': 'deposits (L1 > L2)', - '/output-roots': 'output roots', - '/batches': 'tx batches (L2 blocks)', - '/batches/[number]': 'L2 tx batch %number%', - '/blobs/[hash]': 'blob %hash% details', - '/ops': 'user operations', - '/op/[hash]': 'user operation %hash%', - '/404': 'error - page not found', - '/name-domains': 'domains search and resolve', - '/name-domains/[name]': '%name% domain details', - '/validators': 'validators list', - '/gas-tracker': 'gas tracker', + '/': '%network_name% blockchain explorer - View %network_name% stats', + '/txs': '%network_name% transactions - %network_name% explorer', + '/txs/kettle/[hash]': '%network_name% kettle %hash% transactions', + '/tx/[hash]': '%network_name% transaction %hash%', + '/blocks': '%network_name% blocks', + '/block/[height_or_hash]': '%network_name% block %height_or_hash%', + '/accounts': '%network_name% top accounts', + '/address/[hash]': '%network_name% address details for %hash%', + '/verified-contracts': 'Verified %network_name% contracts lookup - %network_name% explorer', + '/contract-verification': '%network_name% verify contract', + '/address/[hash]/contract-verification': '%network_name% contract verification for %hash%', + '/tokens': 'Tokens list - %network_name% explorer', + '/token/[hash]': '%network_name% token details', + '/token/[hash]/instance/[id]': '%network_name% NFT instance', + '/apps': '%network_name% DApps - Explore top apps', + '/apps/[id]': '%network_name% marketplace app', + '/stats': '%network_name% stats - %network_name% network insights', + '/api-docs': '%network_name% API docs - %network_name% developer tools', + '/graphiql': 'GraphQL for %network_name% - %network_name% data query', + '/search-results': '%network_name% search result for %q%', + '/auth/profile': '%network_name% - my profile', + '/account/watchlist': '%network_name% - watchlist', + '/account/api-key': '%network_name% - API keys', + '/account/custom-abi': '%network_name% - custom ABI', + '/account/tag-address': '%network_name% - private tags', + '/account/verified-addresses': '%network_name% - my verified addresses', + '/public-tags/submit': '%network_name% - public tag requests', + '/withdrawals': '%network_name% withdrawals - track on %network_name% explorer', + '/visualize/sol2uml': '%network_name% Solidity UML diagram', + '/csv-export': '%network_name% export data to CSV', + '/deposits': '%network_name% deposits (L1 > L2)', + '/output-roots': '%network_name% output roots', + '/dispute-games': '%network_name% dispute games', + '/batches': '%network_name% tx batches (L2 blocks)', + '/batches/[number]': '%network_name% L2 tx batch %number%', + '/blobs/[hash]': '%network_name% blob %hash% details', + '/ops': 'User operations on %network_name% - %network_name% explorer', + '/op/[hash]': '%network_name% user operation %hash%', + '/404': '%network_name% error - page not found', + '/name-domains': '%network_name% name domains - %network_name% explorer', + '/name-domains/[name]': '%network_name% %name% domain details', + '/validators': '%network_name% validators list', + '/gas-tracker': '%network_name% gas tracker - Current gas fees', // 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', - '/api/healthz': 'node API health check', - '/auth/auth0': 'authentication', - '/auth/unverified-email': 'unverified email', + '/login': '%network_name% login', + '/api/metrics': '%network_name% node API prometheus metrics', + '/api/log': '%network_name% node API request log', + '/api/media-type': '%network_name% node API media type', + '/api/proxy': '%network_name% node API proxy', + '/api/csrf': '%network_name% node API CSRF token', + '/api/healthz': '%network_name% node API health check', + '/auth/auth0': '%network_name% authentication', + '/auth/unverified-email': '%network_name% unverified email', }; const TEMPLATE_MAP_ENHANCED: Partial> = { - '/token/[hash]': '%symbol% token details', - '/token/[hash]/instance/[id]': 'token instance for %symbol%', - '/apps/[id]': '- %app_name%', - '/address/[hash]': 'address details for %domain_name%', + '/token/[hash]': '%network_name% %symbol% token details', + '/token/[hash]/instance/[id]': '%network_name% token instance for %symbol%', + '/apps/[id]': '%network_name% - %app_name%', + '/address/[hash]': '%network_name% address details for %domain_name%', }; export function make(pathname: Route['pathname'], isEnriched = false) { const template = (isEnriched ? TEMPLATE_MAP_ENHANCED[pathname] : undefined) ?? TEMPLATE_MAP[pathname]; + const postfix = config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : ''; - return `%network_name% ${ template }`; + return (template + postfix).trim(); } diff --git a/lib/metadata/types.ts b/lib/metadata/types.ts index b7e7547717..fda74301ba 100644 --- a/lib/metadata/types.ts +++ b/lib/metadata/types.ts @@ -20,4 +20,5 @@ export interface Metadata { description?: string; imageUrl?: string; }; + canonical: string | undefined; } diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index f74a16fb51..e53554756b 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -25,14 +25,15 @@ export const PAGE_TYPE_DICT: Record = { '/account/watchlist': 'Watchlist', '/account/api-key': 'API keys', '/account/custom-abi': 'Custom ABI', - '/account/public-tags-request': 'Public tags', '/account/tag-address': 'Private tags', '/account/verified-addresses': 'Verified addresses', + '/public-tags/submit': 'Submit public tag', '/withdrawals': 'Withdrawals', '/visualize/sol2uml': 'Solidity UML diagram', '/csv-export': 'Export data to CSV file', '/deposits': 'Deposits (L1 > L2)', '/output-roots': 'Output roots', + '/dispute-games': 'Dispute games', '/batches': 'Tx batches (L2 blocks)', '/batches/[number]': 'L2 tx batch details', '/blobs/[hash]': 'Blob details', diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index b901d7cc5e..bc69e5e9dc 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -105,6 +105,10 @@ Type extends EventTypes.PAGE_WIDGET ? ( } | { 'Type': 'Security score'; 'Source': 'Analyzed contracts popup'; + } | { + 'Type': 'Action button'; + 'Info': string; + 'Source': 'Txn' | 'NFT collection' | 'NFT item'; } | { 'Type': 'Address tag'; 'Info': string; @@ -124,7 +128,7 @@ Type extends EventTypes.FILTERS ? { 'Filter name': string; } : Type extends EventTypes.BUTTON_CLICK ? { - 'Content': 'Swap button'; + 'Content': string; 'Source': string; } : Type extends EventTypes.PROMO_BANNER ? { diff --git a/lib/router/removeQueryParam.ts b/lib/router/removeQueryParam.ts index 83daa6bd28..d0a37975c7 100644 --- a/lib/router/removeQueryParam.ts +++ b/lib/router/removeQueryParam.ts @@ -1,8 +1,12 @@ import type { NextRouter } from 'next/router'; export default function removeQueryParam(router: NextRouter, param: string) { - const { pathname, query } = router; + const { pathname, query, asPath } = router; const newQuery = { ...query }; delete newQuery[param]; - router.replace({ pathname, query: newQuery }, undefined, { shallow: true }); + + const hashIndex = asPath.indexOf('#'); + const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : ''; + + router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true }); } diff --git a/lib/router/updateQueryParam.ts b/lib/router/updateQueryParam.ts index de9fadce8b..1dfb1f2a69 100644 --- a/lib/router/updateQueryParam.ts +++ b/lib/router/updateQueryParam.ts @@ -1,8 +1,12 @@ import type { NextRouter } from 'next/router'; export default function updateQueryParam(router: NextRouter, param: string, newValue: string) { - const { pathname, query } = router; + const { pathname, query, asPath } = router; const newQuery = { ...query }; newQuery[param] = newValue; - router.replace({ pathname, query: newQuery }, undefined, { shallow: true }); + + const hashIndex = asPath.indexOf('#'); + const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : ''; + + router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true }); } diff --git a/lib/settings/colorTheme.ts b/lib/settings/colorTheme.ts new file mode 100644 index 0000000000..c5b7edaa1f --- /dev/null +++ b/lib/settings/colorTheme.ts @@ -0,0 +1,42 @@ +import type { ColorMode } from '@chakra-ui/react'; + +import type { ColorThemeId } from 'types/settings'; + +interface ColorTheme { + id: ColorThemeId; + label: string; + colorMode: ColorMode; + hex: string; + sampleBg: string; +} + +export const COLOR_THEMES: Array = [ + { + id: 'light', + label: 'Light', + colorMode: 'light', + hex: '#FFFFFF', + sampleBg: 'linear-gradient(154deg, #EFEFEF 50%, rgba(255, 255, 255, 0.00) 330.86%)', + }, + { + id: 'dim', + label: 'Dim', + colorMode: 'dark', + hex: '#232B37', + sampleBg: 'linear-gradient(152deg, #232B37 50%, rgba(255, 255, 255, 0.00) 290.71%)', + }, + { + id: 'midnight', + label: 'Midnight', + colorMode: 'dark', + hex: '#1B2E48', + sampleBg: 'linear-gradient(148deg, #1B3F71 50%, rgba(255, 255, 255, 0.00) 312.35%)', + }, + { + id: 'dark', + label: 'Dark', + colorMode: 'dark', + hex: '#101112', + sampleBg: 'linear-gradient(161deg, #000 9.37%, #383838 92.52%)', + }, +]; diff --git a/ui/snippets/topBar/settings/utils.ts b/lib/settings/identIcon.ts similarity index 51% rename from ui/snippets/topBar/settings/utils.ts rename to lib/settings/identIcon.ts index 5346846a33..1159308576 100644 --- a/ui/snippets/topBar/settings/utils.ts +++ b/lib/settings/identIcon.ts @@ -1,34 +1,5 @@ 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', diff --git a/lib/token/tokenTypes.ts b/lib/token/tokenTypes.ts index 4b7fabf9ea..a382e61340 100644 --- a/lib/token/tokenTypes.ts +++ b/lib/token/tokenTypes.ts @@ -1,15 +1,23 @@ 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 }> = [ - { title: 'ERC-20', id: 'ERC-20' }, +import config from 'configs/app'; + +const tokenStandardName = config.chain.tokenStandard; + +export const NFT_TOKEN_TYPES: Record = { + 'ERC-721': `${ tokenStandardName }-721`, + 'ERC-1155': `${ tokenStandardName }-1155`, + 'ERC-404': `${ tokenStandardName }-404`, +}; + +export const TOKEN_TYPES: Record = { + 'ERC-20': `${ tokenStandardName }-20`, ...NFT_TOKEN_TYPES, -]; +}; + +export const NFT_TOKEN_TYPE_IDS: Array = [ 'ERC-721', 'ERC-1155', 'ERC-404' ]; +export const TOKEN_TYPE_IDS: Array = [ 'ERC-20', ...NFT_TOKEN_TYPE_IDS ]; -export const NFT_TOKEN_TYPE_IDS = NFT_TOKEN_TYPES.map(i => i.id); -export const TOKEN_TYPE_IDS = TOKEN_TYPES.map(i => i.id); +export function getTokenTypeName(typeId: TokenType) { + return TOKEN_TYPES[typeId]; +} diff --git a/lib/validations/color.ts b/lib/validations/color.ts new file mode 100644 index 0000000000..1f31d02801 --- /dev/null +++ b/lib/validations/color.ts @@ -0,0 +1,17 @@ +export const COLOR_HEX_REGEXP = /^#[A-Fa-f\d]{3,6}$/; + +export const validator = (value: string | undefined) => { + if (!value || value.length === 0) { + return true; + } + + if (value.length !== 4 && value.length !== 7) { + return 'Invalid length'; + } + + if (!COLOR_HEX_REGEXP.test(value)) { + return 'Invalid hex code'; + } + + return true; +}; diff --git a/middleware.ts b/middleware.ts index 17648c82f8..2f017d9cd5 100644 --- a/middleware.ts +++ b/middleware.ts @@ -19,8 +19,12 @@ export function middleware(req: NextRequest) { return accountResponse; } - const end = Date.now(); const res = NextResponse.next(); + + middlewares.colorTheme(req, res); + + const end = Date.now(); + res.headers.append('Content-Security-Policy', cspPolicy); res.headers.append('Server-Timing', `middleware;dur=${ end - start }`); res.headers.append('Docker-ID', process.env.HOSTNAME || ''); diff --git a/mocks/address/address.ts b/mocks/address/address.ts index 8ed50dca22..acf56ac64e 100644 --- a/mocks/address/address.ts +++ b/mocks/address/address.ts @@ -8,7 +8,7 @@ export const hash = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859'; export const withName: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'ArianeeStore', @@ -20,7 +20,7 @@ export const withName: AddressParam = { export const withEns: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'ArianeeStore', @@ -32,7 +32,7 @@ export const withEns: AddressParam = { export const withNameTag: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'ArianeeStore', @@ -50,7 +50,7 @@ export const withNameTag: AddressParam = { export const withoutName: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -62,7 +62,7 @@ export const withoutName: AddressParam = { export const token: Address = { hash: hash, - implementation_name: null, + implementations: null, is_contract: true, is_verified: false, name: null, @@ -75,8 +75,7 @@ export const token: Address = { coin_balance: '1', creation_tx_hash: '0xc38cf7377bf72d6436f63c37b01b24d032101f20ec1849286dc703c712f10c98', creator_address_hash: '0x34A9c688512ebdB575e82C50c9803F6ba2916E72', - exchange_rate: null, - implementation_address: null, + exchange_rate: '0.04311', has_decompiled_code: false, has_logs: false, has_token_transfers: true, @@ -97,8 +96,9 @@ export const contract: Address = { has_tokens: false, has_validated_blocks: false, hash: hash, - implementation_address: '0x2F4F4A52295940C576417d29F22EEb92B440eC89', - implementation_name: 'HomeBridge', + implementations: [ + { address: '0x2F4F4A52295940C576417d29F22EEb92B440eC89', name: 'HomeBridge' }, + ], is_contract: true, is_verified: true, name: 'EternalStorageProxy', @@ -122,8 +122,7 @@ export const validator: Address = { has_tokens: false, has_validated_blocks: true, hash: hash, - implementation_address: null, - implementation_name: null, + implementations: [], is_contract: false, is_verified: false, name: 'Kiryl Ihnatsyeu', diff --git a/mocks/address/tokens.ts b/mocks/address/tokens.ts index f3fd58b8d5..c494c9bbe3 100644 --- a/mocks/address/tokens.ts +++ b/mocks/address/tokens.ts @@ -1,4 +1,4 @@ -import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance } from 'types/api/address'; +import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance, AddressTokensResponse } from 'types/api/address'; import * as tokens from 'mocks/tokens/tokenInfo'; import * as tokenInstance from 'mocks/tokens/tokenInstance'; @@ -119,35 +119,39 @@ export const erc404b: AddressTokenBalance = { token_id: null, }; -export const erc20List = { +export const erc20List: AddressTokensResponse = { items: [ erc20a, erc20b, erc20c, ], + next_page_params: null, }; -export const erc721List = { +export const erc721List: AddressTokensResponse = { items: [ erc721a, erc721b, erc721c, ], + next_page_params: null, }; -export const erc1155List = { +export const erc1155List: AddressTokensResponse = { items: [ erc1155withoutName, erc1155a, erc1155b, ], + next_page_params: null, }; -export const erc404List = { +export const erc404List: AddressTokensResponse = { items: [ erc404a, erc404b, ], + next_page_params: null, }; export const nfts: AddressNFTsResponse = { diff --git a/mocks/blocks/block.ts b/mocks/blocks/block.ts index eb182d2ebf..2676c72c0e 100644 --- a/mocks/blocks/block.ts +++ b/mocks/blocks/block.ts @@ -15,7 +15,7 @@ export const base: Block = { height: 30146364, miner: { hash: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'Alex Emelyanov', @@ -65,7 +65,7 @@ export const genesis: Block = { height: 0, miner: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -94,7 +94,7 @@ export const base2: Block = { size: 592, miner: { hash: '0xDfE10D55d9248B2ED66f1647df0b0A46dEb25165', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'Kiryl Ihnatsyeu', diff --git a/mocks/contract/info.ts b/mocks/contract/info.ts index 4ff831e168..99f87f8742 100644 --- a/mocks/contract/info.ts +++ b/mocks/contract/info.ts @@ -51,6 +51,11 @@ export const verified: SmartContract = { minimal_proxy_address_hash: null, }; +export const certified: SmartContract = { + ...verified, + certified: true, +}; + export const withMultiplePaths: SmartContract = { ...verified, file_path: './simple_storage.sol', diff --git a/mocks/contract/solidityscanReport.ts b/mocks/contract/solidityscanReport.ts index 000adabf7f..41cbc785f2 100644 --- a/mocks/contract/solidityscanReport.ts +++ b/mocks/contract/solidityscanReport.ts @@ -1,5 +1,8 @@ -export const solidityscanReportAverage = { +import type { SolidityscanReport } from 'types/api/contract'; + +export const solidityscanReportAverage: SolidityscanReport = { scan_report: { + contractname: 'foo', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { @@ -20,8 +23,9 @@ export const solidityscanReportAverage = { }, }; -export const solidityscanReportGreat = { +export const solidityscanReportGreat: SolidityscanReport = { scan_report: { + contractname: 'foo', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { @@ -42,8 +46,9 @@ export const solidityscanReportGreat = { }, }; -export const solidityscanReportLow = { +export const solidityscanReportLow: SolidityscanReport = { scan_report: { + contractname: 'foo', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { diff --git a/mocks/contracts/index.ts b/mocks/contracts/index.ts index bc3b4ecfb2..86a0a0855c 100644 --- a/mocks/contracts/index.ts +++ b/mocks/contracts/index.ts @@ -3,7 +3,7 @@ import type { VerifiedContract, VerifiedContractsResponse } from 'types/api/cont export const contract1: VerifiedContract = { address: { hash: '0xef490030ac0d53B70E304b6Bc5bF657dc6780bEB', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'MockERC20', @@ -26,7 +26,7 @@ export const contract1: VerifiedContract = { export const contract2: VerifiedContract = { address: { hash: '0xB2218bdEbe8e90f80D04286772B0968ead666942', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'EternalStorageProxyWithSomeExternalLibrariesAndEvenMore', @@ -35,6 +35,7 @@ export const contract2: VerifiedContract = { watchlist_names: [], ens_domain_name: null, }, + certified: true, coin_balance: '9078234570352343999', compiler_version: 'v0.3.1+commit.0463ea4c', has_constructor_args: true, diff --git a/mocks/l2disputeGames/disputeGames.ts b/mocks/l2disputeGames/disputeGames.ts new file mode 100644 index 0000000000..f522775cac --- /dev/null +++ b/mocks/l2disputeGames/disputeGames.ts @@ -0,0 +1,26 @@ +export const data = { + items: [ + { + contract_address: '0x5cbe1b88b6357e6a8f0821bea72cc0b88c231f1c', + created_at: '2022-05-27T01:13:48.000000Z', + game_type: 0, + index: 6662, + l2_block_number: 12542890, + resolved_at: null, + status: 'In progress', + }, + { + contract_address: '0x5cbe1b88b6357e6a8f0821bea72cc0b88c231f1c', + created_at: '2022-05-27T01:13:48.000000Z', + game_type: 0, + index: 6662, + l2_block_number: 12542890, + resolved_at: '2022-05-27T01:13:48.000000Z', + status: 'Defender wins', + }, + ], + next_page_params: { + items_count: 50, + index: 8382363, + }, +}; diff --git a/mocks/l2withdrawals/withdrawals.ts b/mocks/l2withdrawals/withdrawals.ts index 8882f8515d..046b467402 100644 --- a/mocks/l2withdrawals/withdrawals.ts +++ b/mocks/l2withdrawals/withdrawals.ts @@ -6,7 +6,7 @@ export const data: OptimisticL2WithdrawalsResponse = { challenge_period_end: null, from: { hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/metadata/address.ts b/mocks/metadata/address.ts index 4e7849bb50..f708fcb319 100644 --- a/mocks/metadata/address.ts +++ b/mocks/metadata/address.ts @@ -61,3 +61,32 @@ export const protocolTag: AddressMetadataTagApi = { ordinal: 0, meta: null, }; + +export const protocolTagWithMeta: AddressMetadataTagApi = { + slug: 'uniswap', + name: 'Uniswap', + tagType: 'protocol', + ordinal: 0, + meta: { + appID: 'uniswap', + appMarketplaceURL: 'https://example.com', + appLogoURL: 'https://localhost:3100/icon.svg', + appActionButtonText: 'Swap', + textColor: '#FFFFFF', + bgColor: '#FF007A', + }, +}; + +export const warpcastTag: AddressMetadataTagApi = { + slug: 'warpcast-account', + name: 'Farcaster', + tagType: 'protocol', + ordinal: 0, + meta: { + bgColor: '#8465CB', + tagIcon: 'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%2032%2029%22%3E%3Cpath%20d%3D%22M%205.507%200.072%20L%2026.097%200.072%20L%2026.097%204.167%20L%2031.952%204.167%20L%2030.725%208.263%20L%2029.686%208.263%20L%2029.686%2024.833%20C%2030.207%2024.833%2030.63%2025.249%2030.63%2025.763%20L%2030.63%2026.88%20L%2030.819%2026.88%20C%2031.341%2026.88%2031.764%2027.297%2031.764%2027.811%20L%2031.764%2028.928%20L%2021.185%2028.928%20L%2021.185%2027.811%20C%2021.185%2027.297%2021.608%2026.88%2022.13%2026.88%20L%2022.319%2026.88%20L%2022.319%2025.763%20C%2022.319%2025.316%2022.639%2024.943%2023.065%2024.853%20L%2023.045%2015.71%20C%2022.711%2012.057%2019.596%209.194%2015.802%209.194%20C%2012.008%209.194%208.893%2012.057%208.559%2015.71%20L%208.539%2024.845%20C%209.043%2024.919%209.663%2025.302%209.663%2025.763%20L%209.663%2026.88%20L%209.852%2026.88%20C%2010.373%2026.88%2010.796%2027.297%2010.796%2027.811%20L%2010.796%2028.928%20L%200.218%2028.928%20L%200.218%2027.811%20C%200.218%2027.297%200.641%2026.88%201.162%2026.88%20L%201.351%2026.88%20L%201.351%2025.763%20C%201.351%2025.249%201.774%2024.833%202.296%2024.833%20L%202.296%208.263%20L%201.257%208.263%20L%200.029%204.167%20L%205.507%204.167%20L%205.507%200.072%20Z%22%20fill%3D%22rgb(255%2C%20255%2C%20255)%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M%2026.097%200.072%20L%2026.166%200.072%20L%2026.166%200.004%20L%2026.097%200.004%20Z%20M%205.507%200.072%20L%205.507%200.004%20L%205.438%200.004%20L%205.438%200.072%20Z%20M%2026.097%204.167%20L%2026.028%204.167%20L%2026.028%204.235%20L%2026.097%204.235%20Z%20M%2031.952%204.167%20L%2032.019%204.187%20L%2032.045%204.099%20L%2031.952%204.099%20L%2031.952%204.167%20Z%20M%2030.725%208.263%20L%2030.725%208.331%20L%2030.776%208.331%20L%2030.791%208.282%20Z%20M%2029.686%208.263%20L%2029.686%208.195%20L%2029.617%208.195%20L%2029.617%208.263%20Z%20M%2029.686%2024.833%20L%2029.617%2024.833%20L%2029.617%2024.901%20L%2029.686%2024.901%20Z%20M%2030.63%2026.88%20L%2030.561%2026.88%20L%2030.561%2026.948%20L%2030.63%2026.948%20Z%20M%2031.764%2028.928%20L%2031.764%2028.996%20L%2031.832%2028.996%20L%2031.832%2028.928%20Z%20M%2021.185%2028.928%20L%2021.116%2028.928%20L%2021.116%2028.996%20L%2021.185%2028.996%20Z%20M%2022.319%2026.88%20L%2022.319%2026.948%20L%2022.388%2026.948%20L%2022.388%2026.88%20Z%20M%2023.065%2024.853%20L%2023.08%2024.919%20L%2023.134%2024.908%20L%2023.134%2024.853%20Z%20M%2023.045%2015.71%20L%2023.114%2015.71%20L%2023.114%2015.707%20L%2023.113%2015.704%20Z%20M%208.559%2015.71%20L%208.49%2015.704%20L%208.49%2015.707%20L%208.49%2015.71%20Z%20M%208.539%2024.845%20L%208.47%2024.845%20L%208.469%2024.904%20L%208.528%2024.913%20Z%20M%209.663%2026.88%20L%209.594%2026.88%20L%209.594%2026.948%20L%209.663%2026.948%20Z%20M%2010.796%2028.928%20L%2010.796%2028.996%20L%2010.865%2028.996%20L%2010.865%2028.928%20Z%20M%200.218%2028.928%20L%200.149%2028.928%20L%200.149%2028.996%20L%200.218%2028.996%20Z%20M%201.351%2026.88%20L%201.351%2026.948%20L%201.42%2026.948%20L%201.42%2026.88%20Z%20M%202.296%2024.833%20L%202.296%2024.901%20L%202.365%2024.901%20L%202.365%2024.833%20Z%20M%202.296%208.263%20L%202.365%208.263%20L%202.365%208.195%20L%202.296%208.195%20Z%20M%201.257%208.263%20L%201.191%208.282%20L%201.205%208.331%20L%201.257%208.331%20Z%20M%200.029%204.167%20L%200.029%204.1%20L%20-0.063%204.1%20L%20-0.037%204.187%20L%200.029%204.167%20Z%20M%205.507%204.167%20L%205.507%204.235%20L%205.576%204.235%20L%205.576%204.167%20Z%20M%2026.097%200.004%20L%205.507%200.004%20L%205.507%200.139%20L%2026.097%200.139%20Z%20M%2026.166%204.167%20L%2026.166%200.072%20L%2026.028%200.072%20L%2026.028%204.167%20L%2026.166%204.167%20Z%20M%2031.952%204.099%20L%2026.097%204.099%20L%2026.097%204.235%20L%2031.952%204.235%20Z%20M%2030.791%208.282%20L%2032.019%204.187%20L%2031.886%204.148%20L%2030.658%208.244%20Z%20M%2029.686%208.331%20L%2030.725%208.331%20L%2030.725%208.195%20L%2029.686%208.195%20Z%20M%2029.755%2024.833%20L%2029.755%208.263%20L%2029.617%208.263%20L%2029.617%2024.833%20Z%20M%2030.699%2025.763%20C%2030.699%2025.212%2030.245%2024.765%2029.686%2024.765%20L%2029.686%2024.9%20C%2030.169%2024.9%2030.561%2025.287%2030.561%2025.763%20Z%20M%2030.699%2026.88%20L%2030.699%2025.763%20L%2030.561%2025.763%20L%2030.561%2026.88%20Z%20M%2030.819%2026.813%20L%2030.63%2026.813%20L%2030.63%2026.948%20L%2030.819%2026.948%20Z%20M%2031.832%2027.811%20C%2031.832%2027.26%2031.379%2026.813%2030.819%2026.813%20L%2030.819%2026.948%20C%2031.303%2026.948%2031.695%2027.335%2031.695%2027.811%20Z%20M%2031.832%2028.928%20L%2031.832%2027.811%20L%2031.695%2027.811%20L%2031.695%2028.928%20Z%20M%2026.097%2028.996%20L%2031.764%2028.996%20L%2031.764%2028.86%20L%2026.097%2028.86%20Z%20M%2023.074%2028.996%20L%2026.097%2028.996%20L%2026.097%2028.86%20L%2023.074%2028.86%20Z%20M%2021.185%2028.996%20L%2023.074%2028.996%20L%2023.074%2028.86%20L%2021.185%2028.86%20Z%20M%2021.116%2027.811%20L%2021.116%2028.928%20L%2021.254%2028.928%20L%2021.254%2027.811%20Z%20M%2022.13%2026.813%20C%2021.57%2026.813%2021.116%2027.26%2021.116%2027.811%20L%2021.254%2027.811%20C%2021.254%2027.335%2021.646%2026.948%2022.13%2026.948%20Z%20M%2022.319%2026.813%20L%2022.13%2026.813%20L%2022.13%2026.948%20L%2022.319%2026.948%20Z%20M%2022.25%2025.763%20L%2022.25%2026.88%20L%2022.388%2026.88%20L%2022.388%2025.763%20Z%20M%2023.051%2024.787%20C%2022.593%2024.883%2022.25%2025.284%2022.25%2025.763%20L%2022.388%2025.763%20C%2022.388%2025.349%2022.684%2025.003%2023.08%2024.919%20Z%20M%2022.976%2015.71%20L%2022.996%2024.853%20L%2023.134%2024.853%20L%2023.114%2015.71%20Z%20M%2015.802%209.262%20C%2019.559%209.262%2022.645%2012.098%2022.976%2015.716%20L%2023.113%2015.704%20C%2022.776%2012.016%2019.632%209.126%2015.802%209.126%20Z%20M%208.628%2015.716%20C%208.959%2012.098%2012.044%209.262%2015.802%209.262%20L%2015.802%209.126%20C%2011.972%209.126%208.828%2012.016%208.49%2015.704%20Z%20M%208.608%2024.845%20L%208.628%2015.71%20L%208.49%2015.71%20L%208.47%2024.845%20Z%20M%209.732%2025.763%20C%209.732%2025.502%209.557%2025.273%209.331%2025.105%20C%209.104%2024.935%208.812%2024.817%208.549%2024.778%20L%208.528%2024.912%20C%208.769%2024.948%209.039%2025.057%209.248%2025.213%20C%209.459%2025.37%209.594%2025.563%209.594%2025.763%20Z%20M%209.732%2026.88%20L%209.732%2025.763%20L%209.594%2025.763%20L%209.594%2026.88%20Z%20M%209.852%2026.813%20L%209.663%2026.813%20L%209.663%2026.948%20L%209.852%2026.948%20Z%20M%2010.865%2027.811%20C%2010.865%2027.26%2010.411%2026.813%209.852%2026.813%20L%209.852%2026.948%20C%2010.335%2026.948%2010.727%2027.335%2010.727%2027.811%20Z%20M%2010.865%2028.928%20L%2010.865%2027.811%20L%2010.727%2027.811%20L%2010.727%2028.928%20Z%20M%208.529%2028.996%20L%2010.796%2028.996%20L%2010.796%2028.86%20L%208.529%2028.86%20Z%20M%208.372%2028.996%20L%208.529%2028.996%20L%208.529%2028.86%20L%208.372%2028.86%20Z%20M%205.507%2028.996%20L%208.372%2028.996%20L%208.372%2028.86%20L%205.507%2028.86%20Z%20M%200.218%2028.996%20L%205.507%2028.996%20L%205.507%2028.86%20L%200.218%2028.86%20Z%20M%200.149%2027.811%20L%200.149%2028.928%20L%200.287%2028.928%20L%200.287%2027.811%20Z%20M%201.162%2026.813%20C%200.603%2026.813%200.149%2027.26%200.149%2027.811%20L%200.287%2027.811%20C%200.287%2027.335%200.679%2026.948%201.162%2026.948%20Z%20M%201.351%2026.813%20L%201.162%2026.813%20L%201.162%2026.948%20L%201.351%2026.948%20Z%20M%201.282%2025.763%20L%201.282%2026.88%20L%201.42%2026.88%20L%201.42%2025.763%20Z%20M%202.296%2024.765%20C%201.736%2024.765%201.282%2025.212%201.282%2025.763%20L%201.42%2025.763%20C%201.42%2025.287%201.812%2024.9%202.296%2024.9%20Z%20M%202.227%208.263%20L%202.227%2024.833%20L%202.365%2024.833%20L%202.365%208.263%20Z%20M%201.257%208.331%20L%202.296%208.331%20L%202.296%208.195%20L%201.257%208.195%20Z%20M%20-0.037%204.187%20L%201.191%208.282%20L%201.323%208.244%20L%200.095%204.148%20Z%20M%205.507%204.099%20L%200.029%204.099%20L%200.029%204.235%20L%205.507%204.235%20L%205.507%204.099%20Z%20M%205.438%200.072%20L%205.438%204.167%20L%205.576%204.167%20L%205.576%200.072%20Z%22%20fill%3D%22rgb(255%2C255%2C255)%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E', tagUrl: 'https://warpcast.com/mbj357', + textColor: '#FFFFFF', + tooltipDescription: 'This address is linked to a Farcaster account', + warpcastHandle: 'duckYduck', + }, +}; diff --git a/mocks/metadata/appActionButton.ts b/mocks/metadata/appActionButton.ts new file mode 100644 index 0000000000..47938638cd --- /dev/null +++ b/mocks/metadata/appActionButton.ts @@ -0,0 +1,38 @@ +import type { AddressMetadataTagApi } from 'types/api/addressMetadata'; + +const appID = 'uniswap'; +const appMarketplaceURL = 'https://example.com'; +export const appLogoURL = 'https://localhost:3100/icon.svg'; +const appActionButtonText = 'Swap'; +const textColor = '#FFFFFF'; +const bgColor = '#FF007A'; + +export const buttonWithoutStyles: AddressMetadataTagApi['meta'] = { + appID, + appMarketplaceURL, + appLogoURL, + appActionButtonText, +}; + +export const linkWithoutStyles: AddressMetadataTagApi['meta'] = { + appMarketplaceURL, + appLogoURL, + appActionButtonText, +}; + +export const buttonWithStyles: AddressMetadataTagApi['meta'] = { + appID, + appMarketplaceURL, + appLogoURL, + appActionButtonText, + textColor, + bgColor, +}; + +export const linkWithStyles: AddressMetadataTagApi['meta'] = { + appMarketplaceURL, + appLogoURL, + appActionButtonText, + textColor, + bgColor, +}; diff --git a/mocks/metadata/publicTagTypes.ts b/mocks/metadata/publicTagTypes.ts new file mode 100644 index 0000000000..80bf92acba --- /dev/null +++ b/mocks/metadata/publicTagTypes.ts @@ -0,0 +1,34 @@ +export const publicTagTypes = { + tagTypes: [ + { + id: '96f9db76-02fc-477d-a003-640a0c5e7e15', + type: 'name' as const, + description: 'Alias for the address', + }, + { + id: 'e75f396e-f52a-44c9-8790-a1dbae496b72', + type: 'generic' as const, + description: 'Group classification for the address', + }, + { + id: '11a2d4f3-412e-4eb7-b663-86c6f48cdec3', + type: 'information' as const, + description: 'Tags with custom data for the address, e.g. additional link to project, or classification details, or minor account details', + }, + { + id: 'd37443d4-748f-4314-a4a0-283b666e9f29', + type: 'classifier' as const, + description: 'E.g. "ERC20", "Contract", "CEX", "DEX", "NFT"', + }, + { + id: 'ea9d0f91-9b46-44ff-be70-128bac468f6f', + type: 'protocol' as const, + description: 'Special tag type for protocol-related contracts, e.g. for bridges', + }, + { + id: 'd2600acb-473c-445f-ac72-ed6fef53e06a', + type: 'note' as const, + description: 'Short general-purpose description for the address', + }, + ], +}; diff --git a/mocks/search/index.ts b/mocks/search/index.ts index 571eb24976..34015b5157 100644 --- a/mocks/search/index.ts +++ b/mocks/search/index.ts @@ -96,6 +96,15 @@ export const contract1: SearchResultAddressOrContract = { url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', }; +export const contract2: SearchResultAddressOrContract = { + address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', + name: 'Super utko', + type: 'contract' as const, + is_smart_contract_verified: true, + certified: true, + url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', +}; + export const label1: SearchResultLabel = { address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', name: 'utko', diff --git a/mocks/shibarium/deposits.ts b/mocks/shibarium/deposits.ts index 98bf9d925d..7081042cd8 100644 --- a/mocks/shibarium/deposits.ts +++ b/mocks/shibarium/deposits.ts @@ -8,7 +8,7 @@ export const data: ShibariumDepositsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -25,7 +25,7 @@ export const data: ShibariumDepositsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -42,7 +42,7 @@ export const data: ShibariumDepositsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/shibarium/withdrawals.ts b/mocks/shibarium/withdrawals.ts index 79851f3d1b..6c1e875264 100644 --- a/mocks/shibarium/withdrawals.ts +++ b/mocks/shibarium/withdrawals.ts @@ -8,7 +8,7 @@ export const data: ShibariumWithdrawalsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -25,7 +25,7 @@ export const data: ShibariumWithdrawalsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -42,7 +42,7 @@ export const data: ShibariumWithdrawalsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/stats/index.ts b/mocks/stats/index.ts index 47d13cbb88..e6fecc8d98 100644 --- a/mocks/stats/index.ts +++ b/mocks/stats/index.ts @@ -1,9 +1,12 @@ import _mapValues from 'lodash/mapValues'; -export const base = { +import type { HomeStats } from 'types/api/stats'; + +export const base: HomeStats = { average_block_time: 6212.0, coin_price: '0.00199678', coin_price_change_percentage: -7.42, + coin_image: 'http://localhost:3100/utia.jpg', gas_prices: { average: { fiat_price: '1.39', @@ -41,35 +44,42 @@ export const base = { tvl: '1767425.102766552', }; -export const withBtcLocked = { +export const withBtcLocked: HomeStats = { ...base, rootstock_locked_btc: '3337493406696977561374', }; -export const withoutFiatPrices = { +export const withoutFiatPrices: HomeStats = { ...base, gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null), }; -export const withoutGweiPrices = { +export const withoutGweiPrices: HomeStats = { ...base, gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null), }; -export const withoutBothPrices = { +export const withoutBothPrices: HomeStats = { ...base, gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null), }; -export const withSecondaryCoin = { +export const withSecondaryCoin: HomeStats = { ...base, secondary_coin_price: '3.398', }; -export const noChartData = { +export const noChartData: HomeStats = { ...base, transactions_today: null, coin_price: null, market_cap: null, tvl: null, }; + +export const indexingStatus = { + finished_indexing_blocks: false, + indexed_blocks_ratio: '0.1', + finished_indexing: true, + indexed_internal_transactions_ratio: '1', +}; diff --git a/mocks/tokens/tokenInfo.ts b/mocks/tokens/tokenInfo.ts index a732712775..a22c7537e5 100644 --- a/mocks/tokens/tokenInfo.ts +++ b/mocks/tokens/tokenInfo.ts @@ -8,7 +8,7 @@ export const tokenInfo: TokenInfo = { holders: '46554', name: 'ARIANEE', symbol: 'ARIA', - type: 'ERC-20', + type: 'ERC-20' as const, total_supply: '1235', icon_url: 'http://localhost:3000/token-icon.png', }; @@ -27,7 +27,7 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = { name: 'hyfi.token', symbol: 'HyFi', total_supply: '369000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: 'http://localhost:3000/token-icon.png', }; @@ -40,7 +40,7 @@ export const tokenInfoERC20b: TokenInfo<'ERC-20'> = { name: 'USD Coin', symbol: 'USDC', total_supply: '900000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -53,7 +53,7 @@ export const tokenInfoERC20c: TokenInfo<'ERC-20'> = { name: 'Ethereum', symbol: 'ETH', total_supply: '1000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -66,7 +66,7 @@ export const tokenInfoERC20d: TokenInfo<'ERC-20'> = { name: 'Zeta', symbol: 'ZETA', total_supply: '2100000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -79,7 +79,7 @@ export const tokenInfoERC20LongSymbol: TokenInfo<'ERC-20'> = { name: 'Zeta', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', total_supply: '2100000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -92,7 +92,7 @@ export const tokenInfoERC721a: TokenInfo<'ERC-721'> = { name: 'HyFi Athena', symbol: 'HYFI_ATHENA', total_supply: '105', - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -105,7 +105,7 @@ export const tokenInfoERC721b: TokenInfo<'ERC-721'> = { name: 'World Of Women Galaxy', symbol: 'WOWG', total_supply: null, - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -118,7 +118,7 @@ export const tokenInfoERC721c: TokenInfo<'ERC-721'> = { name: 'Puma', symbol: 'PUMA', total_supply: null, - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -131,7 +131,7 @@ export const tokenInfoERC721LongSymbol: TokenInfo<'ERC-721'> = { name: 'Puma', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', total_supply: null, - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -144,7 +144,7 @@ export const tokenInfoERC1155a: TokenInfo<'ERC-1155'> = { name: 'HyFi Membership', symbol: 'HYFI_MEMBERSHIP', total_supply: '482', - type: 'ERC-1155', + type: 'ERC-1155' as const, icon_url: null, }; @@ -157,7 +157,7 @@ export const tokenInfoERC1155b: TokenInfo<'ERC-1155'> = { name: 'WinkyVerse Collections', symbol: 'WVC', total_supply: '4943', - type: 'ERC-1155', + type: 'ERC-1155' as const, icon_url: null, }; @@ -170,7 +170,7 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = { name: null, symbol: null, total_supply: '482', - type: 'ERC-1155', + type: 'ERC-1155' as const, icon_url: null, }; @@ -184,7 +184,7 @@ export const tokenInfoERC404: TokenInfo<'ERC-404'> = { name: 'OMNI404', symbol: 'O404', total_supply: '6482275000000000000', - type: 'ERC-404', + type: 'ERC-404' as const, }; export const bridgedTokenA: TokenInfo<'ERC-20'> = { diff --git a/mocks/tokens/tokenTransfer.ts b/mocks/tokens/tokenTransfer.ts index e756d14617..b88cd499ff 100644 --- a/mocks/tokens/tokenTransfer.ts +++ b/mocks/tokens/tokenTransfer.ts @@ -3,7 +3,7 @@ import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransf export const erc20: TokenTransfer = { from: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -14,7 +14,7 @@ export const erc20: TokenTransfer = { }, to: { hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51', - implementation_name: null, + implementations: null, is_contract: true, is_verified: false, name: null, @@ -50,7 +50,7 @@ export const erc20: TokenTransfer = { export const erc721: TokenTransfer = { from: { hash: '0x621C2a125ec4A6D8A7C7A655A18a2868d35eb43C', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -61,7 +61,7 @@ export const erc721: TokenTransfer = { }, to: { hash: '0x47eE48AEBc4ab9Ed908b805b8c8dAAa71B31Db1A', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -96,7 +96,7 @@ export const erc721: TokenTransfer = { export const erc1155A: TokenTransfer = { from: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -107,7 +107,7 @@ export const erc1155A: TokenTransfer = { }, to: { hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -173,7 +173,7 @@ export const erc1155D: TokenTransfer = { export const erc404A: TokenTransfer = { from: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -184,7 +184,7 @@ export const erc404A: TokenTransfer = { }, to: { hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/txs/internalTxs.ts b/mocks/txs/internalTxs.ts index 07eb83dc5e..23fc4bf027 100644 --- a/mocks/txs/internalTxs.ts +++ b/mocks/txs/internalTxs.ts @@ -6,7 +6,7 @@ export const base: InternalTransaction = { error: null, from: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -21,7 +21,7 @@ export const base: InternalTransaction = { timestamp: '2022-10-10T14:43:05.000000Z', to: { hash: '0x502a9C8af2441a1E276909405119FaE21F3dC421', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeCreditHistory', @@ -56,7 +56,7 @@ export const withContractCreated: InternalTransaction = { }, created_contract: { hash: '0xdda21946FF3FAa027104b15BE6970CA756439F5a', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'Shavuha token', diff --git a/mocks/txs/state.ts b/mocks/txs/state.ts index 204fe1c867..d409f4679a 100644 --- a/mocks/txs/state.ts +++ b/mocks/txs/state.ts @@ -1,9 +1,9 @@ -import type { TxStateChange } from 'types/api/txStateChanges'; +import type { TxStateChange, TxStateChanges } from 'types/api/txStateChanges'; export const mintToken: TxStateChange = { address: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -35,13 +35,13 @@ export const mintToken: TxStateChange = { type: 'ERC-721', icon_url: null, }, - type: 'token', + type: 'token' as const, }; export const receiveMintedToken: TxStateChange = { address: { hash: '0xC8F71D0ae51AfBdB009E2eC1Ea8CC9Ee204A42B5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -73,13 +73,13 @@ export const receiveMintedToken: TxStateChange = { type: 'ERC-721', icon_url: null, }, - type: 'token', + type: 'token' as const, }; export const transfer1155Token: TxStateChange = { address: { hash: '0x51243E83Db20F8FC2761D894067A2A9eb7B158DE', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -105,13 +105,13 @@ export const transfer1155Token: TxStateChange = { type: 'ERC-1155', }, token_id: '1', - type: 'token', + type: 'token' as const, }; export const receiveCoin: TxStateChange = { address: { hash: '0x8dC847Af872947Ac18d5d63fA646EB65d4D99560', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -125,13 +125,13 @@ export const receiveCoin: TxStateChange = { change: '29726406604060', is_miner: true, token: null, - type: 'coin', + type: 'coin' as const, }; export const sendCoin: TxStateChange = { address: { hash: '0xC8F71D0ae51AfBdB009E2eC1Ea8CC9Ee204A42B5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -145,13 +145,14 @@ export const sendCoin: TxStateChange = { change: '-3844844822720562', is_miner: false, token: null, - type: 'coin', + type: 'coin' as const, }; -export const sendERC20Token = { +export const sendERC20Token: TxStateChange = { address: { hash: '0x7f6479df95Aa3036a3BE02DB6300ea201ABd9981', - implementation_name: null, + ens_domain_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -173,13 +174,12 @@ export const sendERC20Token = { name: 'Tether USD', symbol: 'USDT', total_supply: '39030615894320966', - type: 'ERC-20', - token_id: null, + type: 'ERC-20' as const, }, - type: 'token', + type: 'token' as const, }; -export const baseResponse = { +export const baseResponse: TxStateChanges = { items: [ mintToken, receiveMintedToken, diff --git a/mocks/txs/tx.ts b/mocks/txs/tx.ts index 0b97508282..11bc973e83 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -22,7 +22,7 @@ export const base: Transaction = { }, from: { hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', - implementation_name: null, + implementations: null, is_contract: false, name: null, is_verified: null, @@ -48,7 +48,7 @@ export const base: Transaction = { timestamp: '2022-10-10T14:34:30.000000Z', to: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: false, is_verified: true, name: null, @@ -92,7 +92,7 @@ export const withContractCreation: Transaction = { to: null, created_contract: { hash: '0xdda21946FF3FAa027104b15BE6970CA756439F5a', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'Shavuha token', @@ -111,7 +111,7 @@ export const withTokenTransfer: Transaction = { hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196', to: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -167,7 +167,7 @@ export const withRawRevertReason: Transaction = { }, to: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_verified: true, is_contract: true, name: 'Bad guy', @@ -283,7 +283,7 @@ export const stabilityTx: Transaction = { stability_fee: { dapp_address: { hash: '0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -308,7 +308,7 @@ export const stabilityTx: Transaction = { total_fee: '68762500000000', validator_address: { hash: '0x1432997a4058acbBe562F3c1E79738c142039044', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, diff --git a/mocks/txs/txInterpretation.ts b/mocks/txs/txInterpretation.ts index e9c1a43b85..061164088d 100644 --- a/mocks/txs/txInterpretation.ts +++ b/mocks/txs/txInterpretation.ts @@ -26,7 +26,7 @@ export const txInterpretation: TxInterpretationResponse = { type: 'address', value: { hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/userOps/userOp.ts b/mocks/userOps/userOp.ts index efb7517187..e7368c20d5 100644 --- a/mocks/userOps/userOp.ts +++ b/mocks/userOps/userOp.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import type { UserOp } from 'types/api/userOps'; export const userOpData: UserOp = { @@ -24,7 +25,7 @@ export const userOpData: UserOp = { sender: { ens_domain_name: null, hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -33,7 +34,7 @@ export const userOpData: UserOp = { entry_point: { ens_domain_name: null, hash: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -47,7 +48,6 @@ export const userOpData: UserOp = { max_fee_per_gas: '1575000898', max_priority_fee_per_gas: '1575000898', nonce: '79', - // eslint-disable-next-line max-len paymaster_and_data: '0x7cea357b5ac0639f89f9e378a1f03aa5005c0a250000000000000000000000000000000000000000000000000000000065b3a8800000000000000000000000000000000000000000000000000000000065aa6e0028fa4c57ac1141bc9ecd8c9243f618ade8ea1db10ab6c1d1798a222a824764ff2269a72ae7a3680fa8b03a80d8a00cdc710eaf37afdcc55f8c9c4defa3fdf2471b', pre_verification_gas: '48396', sender: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', @@ -59,17 +59,43 @@ export const userOpData: UserOp = { bundler: { ens_domain_name: null, hash: '0xd53Eb5203e367BbDD4f72338938224881Fc501Ab', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, }, - // eslint-disable-next-line max-len call_data: '0xb61d27f600000000000000000000000059f6aa952df7f048fd076e33e0ea8bb552d5ffd8000000000000000000000000000000000000000000000000003f3d017500800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000', + execute_call_data: '0x3cf80e6c', + decoded_call_data: { + method_call: 'execute(address dest, uint256 value, bytes func)', + method_id: 'b61d27f6', + parameters: [ + { + name: 'dest', + type: 'address', + value: '0xb0ccffd05f5a87c4c3ceffaa217900422a249915', + }, + { + name: 'value', + type: 'uint256', + value: '0', + }, + { + name: 'func', + type: 'bytes', + value: '0x3cf80e6c', + }, + ], + }, + decoded_execute_call_data: { + method_call: 'advanceEpoch()', + method_id: '3cf80e6c', + parameters: [], + }, paymaster: { ens_domain_name: null, hash: '0x7ceA357B5AC0639F89F9e378a1f03Aa5005C0a25', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, diff --git a/mocks/userOps/userOps.ts b/mocks/userOps/userOps.ts index a58ff6ed74..9a44e62594 100644 --- a/mocks/userOps/userOps.ts +++ b/mocks/userOps/userOps.ts @@ -6,7 +6,7 @@ export const userOpsData: UserOpsResponse = { address: { ens_domain_name: null, hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -22,7 +22,7 @@ export const userOpsData: UserOpsResponse = { address: { ens_domain_name: null, hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -38,7 +38,7 @@ export const userOpsData: UserOpsResponse = { address: { ens_domain_name: null, hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, diff --git a/mocks/withdrawals/withdrawals.ts b/mocks/withdrawals/withdrawals.ts index d58d901390..97742fe3d6 100644 --- a/mocks/withdrawals/withdrawals.ts +++ b/mocks/withdrawals/withdrawals.ts @@ -9,7 +9,7 @@ export const data: WithdrawalsResponse = { index: 11688, receiver: { hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -23,7 +23,7 @@ export const data: WithdrawalsResponse = { index: 11687, receiver: { hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -37,7 +37,7 @@ export const data: WithdrawalsResponse = { index: 11686, receiver: { hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, diff --git a/next.config.js b/next.config.js index 79d1b38dca..36fce8daa1 100644 --- a/next.config.js +++ b/next.config.js @@ -46,7 +46,7 @@ const moduleExports = { output: 'standalone', productionBrowserSourceMaps: true, experimental: { - instrumentationHook: true, + instrumentationHook: process.env.NEXT_OPEN_TELEMETRY_ENABLED === 'true', turbo: { rules: { '*.svg': { diff --git a/nextjs/PageNextJs.tsx b/nextjs/PageNextJs.tsx index fe99868cb7..89e066ca6d 100644 --- a/nextjs/PageNextJs.tsx +++ b/nextjs/PageNextJs.tsx @@ -21,7 +21,7 @@ interface Props { initSentry(); const PageNextJs = (props: Props) => { - const { title, description, opengraph } = metadata.generate(props, props.apiData); + const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData); useGetCsrfToken(); useAdblockDetect(); @@ -34,6 +34,7 @@ const PageNextJs = (props: Props) { title } + { canonical && } { /* OG TAGS */ } diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index d1596cb1c5..5734f85a76 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -56,6 +56,7 @@ export function app(): CspDev.DirectiveDescriptor { getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, getFeaturePayload(config.features.addressVerification)?.api.endpoint, getFeaturePayload(config.features.nameService)?.api.endpoint, + getFeaturePayload(config.features.addressMetadata)?.api.endpoint, marketplaceFeaturePayload && 'api' in marketplaceFeaturePayload ? marketplaceFeaturePayload.api.endpoint : '', // chain RPC server @@ -132,6 +133,10 @@ export function app(): CspDev.DirectiveDescriptor { '*', ], + 'frame-ancestors': [ + KEY_WORDS.SELF, + ], + ...((() => { if (!config.features.sentry.isEnabled) { return {}; diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index d353a9f67b..844c7ddd35 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -14,7 +14,7 @@ export interface Props { query: Route['query']; cookies: string; referrer: string; - adBannerProvider: AdBannerProviders | undefined; + adBannerProvider: AdBannerProviders | null; // if apiData is undefined, Next.js will complain that it is not serializable // so we force it to be always present in the props but it can be null apiData: metadata.ApiData | null; @@ -32,7 +32,7 @@ Promise>> => { return adBannerFeature.provider; } } - return; + return null; })(); return { @@ -40,7 +40,7 @@ Promise>> => { query, cookies: req.headers.cookie || '', referrer: req.headers.referer || '', - adBannerProvider, + adBannerProvider: adBannerProvider, apiData: null, }, }; @@ -240,3 +240,24 @@ export const login: GetServerSideProps = async(context) => { return base(context); }; + +export const publicTagsSubmit: GetServerSideProps = async(context) => { + + if (!config.features.publicTagsSubmission.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const disputeGames: GetServerSideProps = async(context) => { + if (!config.features.faultProofSystem.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; diff --git a/nextjs/middlewares/colorTheme.ts b/nextjs/middlewares/colorTheme.ts new file mode 100644 index 0000000000..dcb8314cae --- /dev/null +++ b/nextjs/middlewares/colorTheme.ts @@ -0,0 +1,15 @@ +import type { NextRequest, NextResponse } from 'next/server'; + +import appConfig from 'configs/app'; +import * as cookiesLib from 'lib/cookies'; + +export default function colorThemeMiddleware(req: NextRequest, res: NextResponse) { + const colorModeCookie = req.cookies.get(cookiesLib.NAMES.COLOR_MODE); + + if (!colorModeCookie) { + if (appConfig.UI.colorTheme.default) { + res.cookies.set(cookiesLib.NAMES.COLOR_MODE, appConfig.UI.colorTheme.default.colorMode, { path: '/' }); + res.cookies.set(cookiesLib.NAMES.COLOR_MODE_HEX, appConfig.UI.colorTheme.default.hex, { path: '/' }); + } + } +} diff --git a/nextjs/middlewares/index.ts b/nextjs/middlewares/index.ts index 1fe6fae29c..b9466373a3 100644 --- a/nextjs/middlewares/index.ts +++ b/nextjs/middlewares/index.ts @@ -1 +1,2 @@ export { account } from './account'; +export { default as colorTheme } from './colorTheme'; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index 5f5a8edd27..c8a72eeae1 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -9,7 +9,6 @@ declare module "nextjs-routes" { | StaticRoute<"/404"> | StaticRoute<"/account/api-key"> | StaticRoute<"/account/custom-abi"> - | StaticRoute<"/account/public-tags-request"> | StaticRoute<"/account/tag-address"> | StaticRoute<"/account/verified-addresses"> | StaticRoute<"/account/watchlist"> @@ -36,6 +35,7 @@ declare module "nextjs-routes" { | StaticRoute<"/contract-verification"> | StaticRoute<"/csv-export"> | StaticRoute<"/deposits"> + | StaticRoute<"/dispute-games"> | StaticRoute<"/gas-tracker"> | StaticRoute<"/graphiql"> | StaticRoute<"/"> @@ -45,6 +45,7 @@ declare module "nextjs-routes" { | DynamicRoute<"/op/[hash]", { "hash": string }> | StaticRoute<"/ops"> | StaticRoute<"/output-roots"> + | StaticRoute<"/public-tags/submit"> | StaticRoute<"/search-results"> | StaticRoute<"/stats"> | DynamicRoute<"/token/[hash]", { "hash": string }> diff --git a/nextjs/redirects.js b/nextjs/redirects.js index 3a0317d0e4..a5908dc12f 100644 --- a/nextjs/redirects.js +++ b/nextjs/redirects.js @@ -49,16 +49,8 @@ const oldUrls = [ destination: '/account/custom-abi', }, { - source: '/account/public_tags_request', - destination: '/account/public-tags-request', - }, - { - source: '/account/public_tags_request/:id/edit', - destination: '/account/public-tags-request', - }, - { - source: '/account/public_tags_request/new', - destination: '/account/public-tags-request', + source: '/account/public-tags-request', + destination: '/public-tags/submit', }, // TRANSACTIONS diff --git a/package.json b/package.json index fde1113c2e..601d5afb2b 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@tanstack/react-query-devtools": "^5.4.3", "@types/papaparse": "^5.3.5", "@types/react-scroll": "^1.8.4", - "@web3modal/wagmi": "4.1.3", + "@web3modal/wagmi": "4.2.1", "bignumber.js": "^9.1.0", "blo": "^1.1.1", "chakra-react-select": "^4.4.3", @@ -103,8 +103,8 @@ "react-scroll": "^1.8.7", "swagger-ui-react": "^5.9.0", "use-font-face-observer": "^1.2.1", - "viem": "2.9.6", - "wagmi": "2.5.16", + "viem": "2.10.9", + "wagmi": "2.9.2", "xss": "^1.0.14" }, "devDependencies": { diff --git a/pages/_app.tsx b/pages/_app.tsx index 38b3c1d358..28520d76a3 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -18,7 +18,6 @@ import { growthBook } from 'lib/growthbook/init'; import useLoadFeatures from 'lib/growthbook/useLoadFeatures'; import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation'; import { SocketProvider } from 'lib/socket/context'; -import theme from 'theme'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import GoogleAnalytics from 'ui/shared/GoogleAnalytics'; import Layout from 'ui/shared/layout/Layout'; @@ -57,7 +56,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => { page }); return ( - + { /* eslint-disable-next-line @next/next/no-sync-scripts */ } -