Commit 77ecda6c authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into swap-button

parents 0dcad9df 504de39d
...@@ -59,7 +59,7 @@ jobs: ...@@ -59,7 +59,7 @@ jobs:
steps: steps:
- name: Getting tags of the two latestest releases - name: Getting tags of the two latestest releases
id: tags id: tags
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
TAG: ${{ inputs.tag }} TAG: ${{ inputs.tag }}
with: with:
...@@ -108,7 +108,7 @@ jobs: ...@@ -108,7 +108,7 @@ jobs:
- name: Looking for commits between two releases - name: Looking for commits between two releases
id: commits id: commits
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
PREVIOUS_TAG: ${{ steps.tags.outputs.previous }} PREVIOUS_TAG: ${{ steps.tags.outputs.previous }}
LATEST_TAG: ${{ steps.tags.outputs.latest }} LATEST_TAG: ${{ steps.tags.outputs.latest }}
...@@ -137,7 +137,7 @@ jobs: ...@@ -137,7 +137,7 @@ jobs:
- name: Looking for issues linked to commits - name: Looking for issues linked to commits
id: linked_issues id: linked_issues
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
COMMITS: ${{ steps.commits.outputs.result }} COMMITS: ${{ steps.commits.outputs.result }}
with: with:
...@@ -225,7 +225,7 @@ jobs: ...@@ -225,7 +225,7 @@ jobs:
- name: Creating label - name: Creating label
id: label_creating id: label_creating
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
LABEL_NAME: ${{ inputs.label_name }} LABEL_NAME: ${{ inputs.label_name }}
LABEL_COLOR: ${{ inputs.label_color }} LABEL_COLOR: ${{ inputs.label_color }}
...@@ -253,7 +253,7 @@ jobs: ...@@ -253,7 +253,7 @@ jobs:
- name: Adding label to issues - name: Adding label to issues
id: labeling_issues id: labeling_issues
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
LABEL_NAME: ${{ inputs.label_name }} LABEL_NAME: ${{ inputs.label_name }}
ISSUES: ${{ steps.linked_issues.outputs.result }} ISSUES: ${{ steps.linked_issues.outputs.result }}
......
...@@ -32,7 +32,7 @@ jobs: ...@@ -32,7 +32,7 @@ jobs:
steps: steps:
- name: Determine if it is the initial version of the pre-release - name: Determine if it is the initial version of the pre-release
id: is_initial id: is_initial
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
TAG: ${{ github.ref_name }} TAG: ${{ github.ref_name }}
with: with:
......
...@@ -37,7 +37,7 @@ jobs: ...@@ -37,7 +37,7 @@ jobs:
steps: steps:
- name: Fetching issues linked to pull request - name: Fetching issues linked to pull request
id: linked_issues id: linked_issues
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
with: with:
......
...@@ -12,7 +12,7 @@ jobs: ...@@ -12,7 +12,7 @@ jobs:
steps: steps:
- name: Remove label - name: Remove label
id: tags id: tags
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
LABEL_NAME: pre-release LABEL_NAME: pre-release
with: with:
......
...@@ -48,7 +48,7 @@ jobs: ...@@ -48,7 +48,7 @@ jobs:
steps: steps:
- name: Getting project info - name: Getting project info
id: project_info id: project_info
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
PROJECT_NAME: ${{ inputs.project_name }} PROJECT_NAME: ${{ inputs.project_name }}
FIELD_NAME: ${{ inputs.field_name }} FIELD_NAME: ${{ inputs.field_name }}
...@@ -131,7 +131,7 @@ jobs: ...@@ -131,7 +131,7 @@ jobs:
- name: Getting project items that linked to the issues - name: Getting project items that linked to the issues
id: items id: items
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
ISSUES: ${{ inputs.issues }} ISSUES: ${{ inputs.issues }}
with: with:
...@@ -193,7 +193,7 @@ jobs: ...@@ -193,7 +193,7 @@ jobs:
- name: Updating field value of the project items - name: Updating field value of the project items
id: updating_items id: updating_items
uses: actions/github-script@v6 uses: actions/github-script@v7
env: env:
ITEMS: ${{ steps.items.outputs.result }} ITEMS: ${{ steps.items.outputs.result }}
PROJECT_ID: ${{ steps.project_info.outputs.id }} PROJECT_ID: ${{ steps.project_info.outputs.id }}
......
...@@ -322,6 +322,7 @@ ...@@ -322,6 +322,7 @@
"eth", "eth",
"rootstock", "rootstock",
"polygon", "polygon",
"zkevm",
"gnosis", "gnosis",
"localhost", "localhost",
], ],
......
...@@ -29,7 +29,7 @@ NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/front ...@@ -29,7 +29,7 @@ NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/front
##views ##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc ## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] NEXT_PUBLIC_NETWORK_EXPLORERS="[{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Blockchair','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/blockchair.png?raw=true','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'Sentio','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/sentio.png?raw=true','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/tenderly.png?raw=true','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/0xPPl.png?raw=true','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/3xpl.png?raw=true','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ]"
# app features # app features
NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_APP_ENV=development
......
...@@ -14,10 +14,10 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether ...@@ -14,10 +14,10 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.llamarpc.com NEXT_PUBLIC_NETWORK_RPC_URL=https://zkevm-rpc.com
# api configuration # api configuration
NEXT_PUBLIC_API_HOST=65.109.173.70 NEXT_PUBLIC_API_HOST=zkevm.blockscout.com
NEXT_PUBLIC_API_PORT=80 NEXT_PUBLIC_API_PORT=80
NEXT_PUBLIC_API_PROTOCOL=http NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_BASE_PATH=/
...@@ -26,7 +26,11 @@ NEXT_PUBLIC_API_BASE_PATH=/ ...@@ -26,7 +26,11 @@ NEXT_PUBLIC_API_BASE_PATH=/
## homepage ## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
## sidebar ## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth.json NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/polygon-mainnet.json
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/polygon.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%)'
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(255, 255, 255, 1)'
## footer ## footer
## misc ## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
...@@ -34,7 +38,6 @@ NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan ...@@ -34,7 +38,6 @@ NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan
NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_HAS_BEACON_CHAIN=true
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 # NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
...@@ -45,4 +48,4 @@ NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.co ...@@ -45,4 +48,4 @@ NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.co
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
# rollup # rollup
NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=true NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=true
NEXT_PUBLIC_L1_BASE_URL=http://65.109.173.70:81 NEXT_PUBLIC_L1_BASE_URL=https://polygon.blockscout.com
...@@ -259,6 +259,7 @@ const footerLinkGroupSchema: yup.ObjectSchema<CustomLinksGroup> = yup ...@@ -259,6 +259,7 @@ const footerLinkGroupSchema: yup.ObjectSchema<CustomLinksGroup> = yup
const networkExplorerSchema: yup.ObjectSchema<NetworkExplorer> = yup const networkExplorerSchema: yup.ObjectSchema<NetworkExplorer> = yup
.object({ .object({
title: yup.string().required(), title: yup.string().required(),
logo: yup.string().test(urlTest),
baseUrl: yup.string().test(urlTest).required(), baseUrl: yup.string().test(urlTest).required(),
paths: yup paths: yup
.object() .object()
......
...@@ -78,7 +78,9 @@ blockscout: ...@@ -78,7 +78,9 @@ blockscout:
INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER: 'true' INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER: 'true'
INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true' INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true'
INDEXER_RECEIPTS_BATCH_SIZE: 50 INDEXER_RECEIPTS_BATCH_SIZE: 50
INDEXER_COIN_BALANCES_BATCH_SIZE: 50 INDEXER_COIN_BALANCES_BATCH_SIZE: 10
INDEXER_TOKEN_BALANCES_BATCH_SIZE: 15
INDEXER_TOKEN_BALANCES_CONCURRENCY: 4
DISABLE_EXCHANGE_RATES: 'true' DISABLE_EXCHANGE_RATES: 'true'
DISABLE_INDEXER: 'false' DISABLE_INDEXER: 'false'
FIRST_BLOCK: '8739119' FIRST_BLOCK: '8739119'
......
...@@ -62,7 +62,7 @@ frontend: ...@@ -62,7 +62,7 @@ frontend:
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]"
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
......
...@@ -270,6 +270,7 @@ Settings for meta tags and OG tags ...@@ -270,6 +270,7 @@ Settings for meta tags and OG tags
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| logo | `string` | URL to explorer logo file. Should be at least 40x40. | - | - | `'https://foo.app/icon.png'` |
| title | `string` | Displayed name of the explorer | Required | - | `Anyblock` | | title | `string` | Displayed name of the explorer | Required | - | `Anyblock` |
| baseUrl | `string` | Base url of the explorer | Required | - | `https://explorer.anyblock.tools` | | baseUrl | `string` | Base url of the explorer | Required | - | `https://explorer.anyblock.tools` |
| paths | `Record<'tx' \| 'block' \| 'address' \| 'token', string>` | Map of explorer entities and their paths | Required | - | `{'tx':'/ethereum/poa/core/tx'}` | | paths | `Record<'tx' \| 'block' \| 'address' \| 'token', string>` | Map of explorer entities and their paths | Required | - | `{'tx':'/ethereum/poa/core/tx'}` |
......
...@@ -75,9 +75,9 @@ export default function useNavItems(): ReturnType { ...@@ -75,9 +75,9 @@ export default function useNavItems(): ReturnType {
blocks, blocks,
{ {
text: 'Txn batches', text: 'Txn batches',
nextRoute: { pathname: '/zkevm-l2-txn-batches' as const }, nextRoute: { pathname: '/batches' as const },
icon: 'txn_batches', icon: 'txn_batches',
isActive: pathname === '/zkevm-l2-txn-batches' || pathname === '/zkevm-l2-txn-batch/[number]', isActive: pathname === '/batches' || pathname === '/batches/[number]',
}, },
].filter(Boolean), ].filter(Boolean),
[ [
...@@ -91,16 +91,16 @@ export default function useNavItems(): ReturnType { ...@@ -91,16 +91,16 @@ export default function useNavItems(): ReturnType {
[ [
txs, txs,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/l2-deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/l2-deposits' }, { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/deposits' },
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/l2-withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/l2-withdrawals' }, { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' },
], ],
[ [
blocks, blocks,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: 'Txn batches', nextRoute: { pathname: '/l2-txn-batches' as const }, icon: 'txn_batches', isActive: pathname === '/l2-txn-batches' }, { text: 'Txn batches', nextRoute: { pathname: '/batches' as const }, icon: 'txn_batches', isActive: pathname === '/batches' },
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: 'Output roots', nextRoute: { pathname: '/l2-output-roots' as const }, icon: 'output_roots', isActive: pathname === '/l2-output-roots' }, { text: 'Output roots', nextRoute: { pathname: '/output-roots' as const }, icon: 'output_roots', isActive: pathname === '/output-roots' },
], ],
[ [
userOps, userOps,
......
...@@ -33,12 +33,10 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -33,12 +33,10 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/withdrawals': 'Root page', '/withdrawals': 'Root page',
'/visualize/sol2uml': 'Regular page', '/visualize/sol2uml': 'Regular page',
'/csv-export': 'Regular page', '/csv-export': 'Regular page',
'/l2-deposits': 'Root page', '/deposits': 'Root page',
'/l2-output-roots': 'Root page', '/output-roots': 'Root page',
'/l2-txn-batches': 'Root page', '/batches': 'Root page',
'/l2-withdrawals': 'Root page', '/batches/[number]': 'Regular page',
'/zkevm-l2-txn-batches': 'Root page',
'/zkevm-l2-txn-batch/[number]': 'Regular page',
'/ops': 'Root page', '/ops': 'Root page',
'/op/[hash]': 'Regular page', '/op/[hash]': 'Regular page',
'/404': 'Regular page', '/404': 'Regular page',
......
...@@ -36,12 +36,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -36,12 +36,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/withdrawals': DEFAULT_TEMPLATE, '/withdrawals': DEFAULT_TEMPLATE,
'/visualize/sol2uml': DEFAULT_TEMPLATE, '/visualize/sol2uml': DEFAULT_TEMPLATE,
'/csv-export': DEFAULT_TEMPLATE, '/csv-export': DEFAULT_TEMPLATE,
'/l2-deposits': DEFAULT_TEMPLATE, '/deposits': DEFAULT_TEMPLATE,
'/l2-output-roots': DEFAULT_TEMPLATE, '/output-roots': DEFAULT_TEMPLATE,
'/l2-txn-batches': DEFAULT_TEMPLATE, '/batches': DEFAULT_TEMPLATE,
'/l2-withdrawals': DEFAULT_TEMPLATE, '/batches/[number]': DEFAULT_TEMPLATE,
'/zkevm-l2-txn-batches': DEFAULT_TEMPLATE,
'/zkevm-l2-txn-batch/[number]': DEFAULT_TEMPLATE,
'/ops': DEFAULT_TEMPLATE, '/ops': DEFAULT_TEMPLATE,
'/op/[hash]': DEFAULT_TEMPLATE, '/op/[hash]': DEFAULT_TEMPLATE,
'/404': DEFAULT_TEMPLATE, '/404': DEFAULT_TEMPLATE,
......
...@@ -31,12 +31,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -31,12 +31,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/withdrawals': 'withdrawals', '/withdrawals': 'withdrawals',
'/visualize/sol2uml': 'Solidity UML diagram', '/visualize/sol2uml': 'Solidity UML diagram',
'/csv-export': 'export data to CSV', '/csv-export': 'export data to CSV',
'/l2-deposits': 'deposits (L1 > L2)', '/deposits': 'deposits (L1 > L2)',
'/l2-output-roots': 'output roots', '/output-roots': 'output roots',
'/l2-txn-batches': 'tx batches (L2 blocks)', '/batches': 'tx batches (L2 blocks)',
'/l2-withdrawals': 'withdrawals (L2 > L1)', '/batches/[number]': 'L2 tx batch %number%',
'/zkevm-l2-txn-batches': 'zkEvm L2 Tx batches',
'/zkevm-l2-txn-batch/[number]': 'zkEvm L2 Tx batch %number%',
'/ops': 'user operations', '/ops': 'user operations',
'/op/[hash]': 'user operation %hash%', '/op/[hash]': 'user operation %hash%',
'/404': 'error - page not found', '/404': 'error - page not found',
......
...@@ -31,12 +31,10 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -31,12 +31,10 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/withdrawals': 'Withdrawals', '/withdrawals': 'Withdrawals',
'/visualize/sol2uml': 'Solidity UML diagram', '/visualize/sol2uml': 'Solidity UML diagram',
'/csv-export': 'Export data to CSV file', '/csv-export': 'Export data to CSV file',
'/l2-deposits': 'Deposits (L1 > L2)', '/deposits': 'Deposits (L1 > L2)',
'/l2-output-roots': 'Output roots', '/output-roots': 'Output roots',
'/l2-txn-batches': 'Tx batches (L2 blocks)', '/batches': 'Tx batches (L2 blocks)',
'/l2-withdrawals': 'Withdrawals (L2 > L1)', '/batches/[number]': 'L2 tx batch details',
'/zkevm-l2-txn-batches': 'ZkEvm L2 Tx batches',
'/zkevm-l2-txn-batch/[number]': 'ZkEvm L2 Tx batch details',
'/ops': 'User operations', '/ops': 'User operations',
'/op/[hash]': 'User operation details', '/op/[hash]': 'User operation details',
'/404': '404', '/404': '404',
......
...@@ -96,7 +96,7 @@ Type extends EventTypes.PAGE_WIDGET ? ( ...@@ -96,7 +96,7 @@ Type extends EventTypes.PAGE_WIDGET ? (
} }
) : ) :
Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? { Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? {
'Type': 'Address click' | 'Token click'; 'Type': 'Address click' | 'Token click' | 'Domain click';
} : } :
Type extends EventTypes.EXPERIMENT_STARTED ? { Type extends EventTypes.EXPERIMENT_STARTED ? {
'Experiment name': string; 'Experiment name': string;
......
...@@ -48,8 +48,8 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => { ...@@ -48,8 +48,8 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
return account(context); return account(context);
}; };
export const beaconChain: GetServerSideProps<Props> = async(context) => { export const withdrawals: GetServerSideProps<Props> = async(context) => {
if (!config.features.beaconChain.isEnabled) { if (!config.features.beaconChain.isEnabled && !config.features.optimisticRollup.isEnabled) {
return { return {
notFound: true, notFound: true,
}; };
...@@ -58,7 +58,17 @@ export const beaconChain: GetServerSideProps<Props> = async(context) => { ...@@ -58,7 +58,17 @@ export const beaconChain: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const L2: GetServerSideProps<Props> = async(context) => { export const rollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.optimisticRollup.isEnabled && !config.features.zkEvmRollup.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const optimisticRollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.optimisticRollup.isEnabled) { if (!config.features.optimisticRollup.isEnabled) {
return { return {
notFound: true, notFound: true,
...@@ -68,7 +78,7 @@ export const L2: GetServerSideProps<Props> = async(context) => { ...@@ -68,7 +78,7 @@ export const L2: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const zkEvmL2: GetServerSideProps<Props> = async(context) => { export const zkEvmRollup: GetServerSideProps<Props> = async(context) => {
if (!config.features.zkEvmRollup.isEnabled) { if (!config.features.zkEvmRollup.isEnabled) {
return { return {
notFound: true, notFound: true,
......
...@@ -26,19 +26,19 @@ declare module "nextjs-routes" { ...@@ -26,19 +26,19 @@ declare module "nextjs-routes" {
| StaticRoute<"/auth/auth0"> | StaticRoute<"/auth/auth0">
| StaticRoute<"/auth/profile"> | StaticRoute<"/auth/profile">
| StaticRoute<"/auth/unverified-email"> | StaticRoute<"/auth/unverified-email">
| DynamicRoute<"/batches/[number]", { "number": string }>
| StaticRoute<"/batches">
| DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }>
| StaticRoute<"/blocks"> | StaticRoute<"/blocks">
| StaticRoute<"/contract-verification"> | StaticRoute<"/contract-verification">
| StaticRoute<"/csv-export"> | StaticRoute<"/csv-export">
| StaticRoute<"/deposits">
| StaticRoute<"/graphiql"> | StaticRoute<"/graphiql">
| StaticRoute<"/"> | StaticRoute<"/">
| StaticRoute<"/l2-deposits">
| StaticRoute<"/l2-output-roots">
| StaticRoute<"/l2-txn-batches">
| StaticRoute<"/l2-withdrawals">
| StaticRoute<"/login"> | StaticRoute<"/login">
| DynamicRoute<"/name-domains/[name]", { "name": string }> | DynamicRoute<"/name-domains/[name]", { "name": string }>
| StaticRoute<"/name-domains"> | StaticRoute<"/name-domains">
| StaticRoute<"/output-roots">
| DynamicRoute<"/op/[hash]", { "hash": string }> | DynamicRoute<"/op/[hash]", { "hash": string }>
| StaticRoute<"/ops"> | StaticRoute<"/ops">
| StaticRoute<"/search-results"> | StaticRoute<"/search-results">
...@@ -51,9 +51,7 @@ declare module "nextjs-routes" { ...@@ -51,9 +51,7 @@ declare module "nextjs-routes" {
| DynamicRoute<"/txs/kettle/[hash]", { "hash": string }> | DynamicRoute<"/txs/kettle/[hash]", { "hash": string }>
| StaticRoute<"/verified-contracts"> | StaticRoute<"/verified-contracts">
| StaticRoute<"/visualize/sol2uml"> | StaticRoute<"/visualize/sol2uml">
| StaticRoute<"/withdrawals"> | StaticRoute<"/withdrawals">;
| DynamicRoute<"/zkevm-l2-txn-batch/[number]", { "number": string }>
| StaticRoute<"/zkevm-l2-txn-batches">;
interface StaticRoute<Pathname> { interface StaticRoute<Pathname> {
pathname: Pathname; pathname: Pathname;
......
...@@ -242,6 +242,32 @@ const oldUrls = [ ...@@ -242,6 +242,32 @@ const oldUrls = [
source: '/token/:hash/write-proxy', source: '/token/:hash/write-proxy',
destination: '/token/:hash?tab=write_proxy', destination: '/token/:hash?tab=write_proxy',
}, },
// ROLLUPs
{
source: '/l2-txn-batches',
destination: '/batches',
},
{
source: '/zkevm-l2-txn-batches',
destination: '/batches',
},
{
source: '/zkevm-l2-txn-batch/:path*',
destination: '/batches/:path*',
},
{
source: '/l2-deposits',
destination: '/deposits',
},
{
source: '/l2-withdrawals',
destination: '/withdrawals',
},
{
source: '/l2-output-roots',
destination: '/output-roots',
},
]; ];
async function redirects() { async function redirects() {
......
...@@ -23,6 +23,7 @@ import Layout from 'ui/shared/layout/Layout'; ...@@ -23,6 +23,7 @@ import Layout from 'ui/shared/layout/Layout';
import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider';
import 'lib/setLocale'; import 'lib/setLocale';
import 'focus-visible/dist/focus-visible';
type AppPropsWithLayout = AppProps & { type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout; Component: NextPageWithLayout;
......
...@@ -9,7 +9,7 @@ const ZkEvmL2TxnBatch = dynamic(() => import('ui/pages/ZkEvmL2TxnBatch'), { ssr: ...@@ -9,7 +9,7 @@ const ZkEvmL2TxnBatch = dynamic(() => import('ui/pages/ZkEvmL2TxnBatch'), { ssr:
const Page: NextPage<Props> = (props: Props) => { const Page: NextPage<Props> = (props: Props) => {
return ( return (
<PageNextJs pathname="/zkevm-l2-txn-batch/[number]" query={ props }> <PageNextJs pathname="/batches/[number]" query={ props }>
<ZkEvmL2TxnBatch/> <ZkEvmL2TxnBatch/>
</PageNextJs> </PageNextJs>
); );
...@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => { ...@@ -17,4 +17,4 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page; export default Page;
export { zkEvmL2 as getServerSideProps } from 'nextjs/getServerSideProps'; export { zkEvmRollup as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -4,16 +4,23 @@ import React from 'react'; ...@@ -4,16 +4,23 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const L2OutputRoots = dynamic(() => import('ui/pages/L2OutputRoots'), { ssr: false }); import config from 'configs/app';
const Batches = dynamic(() => {
if (config.features.zkEvmRollup.isEnabled) {
return import('ui/pages/ZkEvmL2TxnBatches');
}
return import('ui/pages/L2TxnBatches');
}, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/l2-output-roots"> <PageNextJs pathname="/batches">
<L2OutputRoots/> <Batches/>
</PageNextJs> </PageNextJs>
); );
}; };
export default Page; export default Page;
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; export { rollup as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -4,16 +4,16 @@ import React from 'react'; ...@@ -4,16 +4,16 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const L2Deposits = dynamic(() => import('ui/pages/L2Deposits'), { ssr: false }); const Deposits = dynamic(() => import('ui/pages/L2Deposits'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/l2-deposits"> <PageNextJs pathname="/deposits">
<L2Deposits/> <Deposits/>
</PageNextJs> </PageNextJs>
); );
}; };
export default Page; export default Page;
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; export { optimisticRollup as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const L2Withdrawals = dynamic(() => import('ui/pages/L2Withdrawals'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/l2-withdrawals">
<L2Withdrawals/>
</PageNextJs>
);
};
export default Page;
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -4,16 +4,16 @@ import React from 'react'; ...@@ -4,16 +4,16 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const L2TxnBatches = dynamic(() => import('ui/pages/L2TxnBatches'), { ssr: false }); const OutputRoots = dynamic(() => import('ui/pages/L2OutputRoots'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/l2-txn-batches"> <PageNextJs pathname="/output-roots">
<L2TxnBatches/> <OutputRoots/>
</PageNextJs> </PageNextJs>
); );
}; };
export default Page; export default Page;
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; export { optimisticRollup as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -4,7 +4,14 @@ import React from 'react'; ...@@ -4,7 +4,14 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const Withdrawals = dynamic(() => import('ui/pages/Withdrawals'), { ssr: false }); import config from 'configs/app';
const Withdrawals = dynamic(() => {
if (config.features.optimisticRollup.isEnabled) {
return import('ui/pages/L2Withdrawals');
}
return import('ui/pages/Withdrawals');
}, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
...@@ -16,4 +23,4 @@ const Page: NextPage = () => { ...@@ -16,4 +23,4 @@ const Page: NextPage = () => {
export default Page; export default Page;
export { beaconChain as getServerSideProps } from 'nextjs/getServerSideProps'; export { withdrawals as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const ZkEvmL2TxnBatches = dynamic(() => import('ui/pages/ZkEvmL2TxnBatches'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/zkevm-l2-txn-batches">
<ZkEvmL2TxnBatches/>
</PageNextJs>
);
};
export default Page;
export { zkEvmL2 as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -9,7 +9,7 @@ interface Feature { ...@@ -9,7 +9,7 @@ interface Feature {
export default function contextWithFeaturesFixture(envs: Array<Feature>): Parameters<typeof test.extend>[0]['context'] { export default function contextWithFeaturesFixture(envs: Array<Feature>): Parameters<typeof test.extend>[0]['context'] {
return async({ browser }, use) => { return async({ browser }, use) => {
const storageItems = envs.map(({ id, value }) => ({ name: `pw_feature:${ id }`, value: JSON.stringify({ value, type: typeof value }) })); const storageItems = envs.map(({ id, value }) => ({ name: `pw_feature:${ id }`, value: JSON.stringify(value) }));
const context = await createContextWithStorage(browser, storageItems); const context = await createContextWithStorage(browser, storageItems);
await use(context); await use(context);
......
const useFeatureValue = (name, fallback) => { const useFeatureValue = (name, fallback) => {
try { try {
const { value, type } = JSON.parse(localStorage.getItem(`pw_feature:${ name }`)); const value = JSON.parse(localStorage.getItem(`pw_feature:${ name }`));
return { isLoading: false, value };
const formattedValue = (() => {
switch (type) {
case 'boolean': {
return value === 'true';
}
case 'number': {
return Number(value);
}
default:
return value;
}
})();
return { isLoading: false, value: formattedValue };
} catch (error) { } catch (error) {
return { isLoading: false, value: fallback }; return { isLoading: false, value: fallback };
} }
......
...@@ -7,13 +7,13 @@ export type Erc20TotalPayload = { ...@@ -7,13 +7,13 @@ export type Erc20TotalPayload = {
} }
export type Erc721TotalPayload = { export type Erc721TotalPayload = {
token_id: string; token_id: string | null;
} }
export type Erc1155TotalPayload = { export type Erc1155TotalPayload = {
decimals: string | null; decimals: string | null;
value: string; value: string;
token_id: string; token_id: string | null;
} }
export type TokenTransfer = ( export type TokenTransfer = (
......
...@@ -17,9 +17,10 @@ export type TxInterpretationVariable = ...@@ -17,9 +17,10 @@ export type TxInterpretationVariable =
TxInterpretationVariableCurrency | TxInterpretationVariableCurrency |
TxInterpretationVariableTimestamp | TxInterpretationVariableTimestamp |
TxInterpretationVariableToken | TxInterpretationVariableToken |
TxInterpretationVariableAddress; TxInterpretationVariableAddress |
TxInterpretationVariableDomain;
export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address'; export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address' | 'domain';
export type TxInterpretationVariableString = { export type TxInterpretationVariableString = {
type: 'string'; type: 'string';
...@@ -45,3 +46,8 @@ export type TxInterpretationVariableAddress = { ...@@ -45,3 +46,8 @@ export type TxInterpretationVariableAddress = {
type: 'address'; type: 'address';
value: AddressParam; value: AddressParam;
} }
export type TxInterpretationVariableDomain = {
type: 'domain';
value: string;
}
...@@ -13,6 +13,7 @@ export interface FeaturedNetwork { ...@@ -13,6 +13,7 @@ export interface FeaturedNetwork {
} }
export interface NetworkExplorer { export interface NetworkExplorer {
logo?: string;
title: string; title: string;
baseUrl: string; baseUrl: string;
paths: { paths: {
......
...@@ -45,7 +45,7 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea ...@@ -45,7 +45,7 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea
<Text whiteSpace="pre" variant="secondary" fontWeight={ 400 }> ({ prefix }${ usd.toFormat(2) })</Text> <Text whiteSpace="pre" variant="secondary" fontWeight={ 400 }> ({ prefix }${ usd.toFormat(2) })</Text>
<IconSvg name="arrows/east-mini" transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 3 }/> <IconSvg name="arrows/east-mini" transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 3 }/>
</Button> </Button>
{ isLoading && !isOpen && <Skeleton h="100%" w="100%" position="absolute" top={ 0 } left={ 0 } bgColor={ skeletonBgColor }/> } { isLoading && !isOpen && <Skeleton h="100%" w="100%" position="absolute" top={ 0 } left={ 0 } bgColor={ skeletonBgColor } borderRadius="base"/> }
</Box> </Box>
); );
}; };
......
...@@ -43,9 +43,9 @@ const AddressesListItem = ({ ...@@ -43,9 +43,9 @@ const AddressesListItem = ({
{ item.public_tags !== null && item.public_tags.length > 0 && item.public_tags.map(tag => ( { item.public_tags !== null && item.public_tags.length > 0 && item.public_tags.map(tag => (
<Tag key={ tag.label } isLoading={ isLoading }>{ tag.display_name }</Tag> <Tag key={ tag.label } isLoading={ isLoading }>{ tag.display_name }</Tag>
)) } )) }
<HStack spacing={ 3 }> <HStack spacing={ 3 } maxW="100%" alignItems="flex-start">
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>{ `Balance ${ currencyUnits.ether }` }</Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 } flexShrink={ 0 }>{ `Balance ${ currencyUnits.ether }` }</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" minW="0" whiteSpace="pre-wrap">
<span>{ addressBalance.dp(8).toFormat() }</span> <span>{ addressBalance.dp(8).toFormat() }</span>
</Skeleton> </Skeleton>
</HStack> </HStack>
......
...@@ -34,11 +34,12 @@ const AddressesTableItem = ({ ...@@ -34,11 +34,12 @@ const AddressesTableItem = ({
{ index } { index }
</Skeleton> </Skeleton>
</Td> </Td>
<Td verticalAlign="middle"> <Td>
<AddressEntity <AddressEntity
address={ item } address={ item }
isLoading={ isLoading } isLoading={ isLoading }
fontWeight={ 700 } fontWeight={ 700 }
my="2px"
/> />
</Td> </Td>
<Td pl={ 10 }> <Td pl={ 10 }>
...@@ -47,7 +48,7 @@ const AddressesTableItem = ({ ...@@ -47,7 +48,7 @@ const AddressesTableItem = ({
)) : null } )) : null }
</Td> </Td>
<Td isNumeric> <Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block"> <Skeleton isLoaded={ !isLoading } display="inline-block" maxW="100%">
<Text lineHeight="24px" as="span">{ addressBalanceChunks[0] }</Text> <Text lineHeight="24px" as="span">{ addressBalanceChunks[0] }</Text>
{ addressBalanceChunks[1] && <Text lineHeight="24px" as="span">.</Text> } { addressBalanceChunks[1] && <Text lineHeight="24px" as="span">.</Text> }
<Text lineHeight="24px" variant="secondary" as="span">{ addressBalanceChunks[1] }</Text> <Text lineHeight="24px" variant="secondary" as="span">{ addressBalanceChunks[1] }</Text>
......
...@@ -58,7 +58,7 @@ const LatestDeposits = () => { ...@@ -58,7 +58,7 @@ const LatestDeposits = () => {
} }
if (data) { if (data) {
const depositsUrl = route({ pathname: '/l2-deposits' }); const depositsUrl = route({ pathname: '/deposits' });
return ( return (
<> <>
<SocketNewItemsNotice borderBottomRadius={ 0 } url={ depositsUrl } num={ num } alert={ socketAlert } type="deposit" isLoading={ isPlaceholderData }/> <SocketNewItemsNotice borderBottomRadius={ 0 } url={ depositsUrl } num={ num } alert={ socketAlert } type="deposit" isLoading={ isPlaceholderData }/>
......
...@@ -73,7 +73,7 @@ const LatestZkEvmL2Batches = () => { ...@@ -73,7 +73,7 @@ const LatestZkEvmL2Batches = () => {
</AnimatePresence> </AnimatePresence>
</VStack> </VStack>
<Flex justifyContent="center"> <Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ route({ pathname: '/zkevm-l2-txn-batches' }) }>View all batches</LinkInternal> <LinkInternal fontSize="sm" href={ route({ pathname: '/batches' }) }>View all batches</LinkInternal>
</Flex> </Flex>
</> </>
); );
......
...@@ -57,7 +57,7 @@ const LatestZkevmL2BatchItem = ({ batch, isLoading }: Props) => { ...@@ -57,7 +57,7 @@ const LatestZkevmL2BatchItem = ({ batch, isLoading }: Props) => {
<Flex alignItems="center"> <Flex alignItems="center">
<Skeleton isLoaded={ !isLoading } mr={ 2 }>Txn</Skeleton> <Skeleton isLoaded={ !isLoading } mr={ 2 }>Txn</Skeleton>
<LinkInternal <LinkInternal
href={ route({ pathname: '/zkevm-l2-txn-batch/[number]', query: { number: batch.number.toString(), tab: 'txs' } }) } href={ route({ pathname: '/batches/[number]', query: { number: batch.number.toString(), tab: 'txs' } }) }
isLoading={ isLoading } isLoading={ isLoading }
> >
<Skeleton isLoaded={ !isLoading }> <Skeleton isLoaded={ !isLoading }>
......
...@@ -73,7 +73,7 @@ const Stats = () => { ...@@ -73,7 +73,7 @@ const Stats = () => {
icon="txn_batches" icon="txn_batches"
title="Latest batch" title="Latest batch"
value={ (zkEvmLatestBatchQuery.data || 0).toLocaleString() } value={ (zkEvmLatestBatchQuery.data || 0).toLocaleString() }
url={ route({ pathname: '/zkevm-l2-txn-batches' }) } url={ route({ pathname: '/batches' }) }
isLoading={ zkEvmLatestBatchQuery.isPlaceholderData } isLoading={ zkEvmLatestBatchQuery.isPlaceholderData }
/> />
) : ( ) : (
......
...@@ -9,7 +9,6 @@ import { route } from 'nextjs-routes'; ...@@ -9,7 +9,6 @@ import { route } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { ENS_DOMAIN } from 'stubs/ENS'; import { ENS_DOMAIN } from 'stubs/ENS';
import NameDomainDetails from 'ui/nameDomain/NameDomainDetails'; import NameDomainDetails from 'ui/nameDomain/NameDomainDetails';
...@@ -25,7 +24,6 @@ import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; ...@@ -25,7 +24,6 @@ import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import useTabIndexFromQuery from 'ui/shared/Tabs/useTabIndexFromQuery'; import useTabIndexFromQuery from 'ui/shared/Tabs/useTabIndexFromQuery';
const NameDomain = () => { const NameDomain = () => {
const isMobile = useIsMobile();
const router = useRouter(); const router = useRouter();
const domainName = getQueryParamString(router.query.name); const domainName = getQueryParamString(router.query.name);
...@@ -48,22 +46,28 @@ const NameDomain = () => { ...@@ -48,22 +46,28 @@ const NameDomain = () => {
const isLoading = infoQuery.isPlaceholderData; const isLoading = infoQuery.isPlaceholderData;
const titleSecondRow = ( const titleSecondRow = (
<Flex columnGap={ 3 } rowGap={ 3 } fontFamily="heading" fontSize="lg" fontWeight={ 500 } alignItems="center" w="100%"> <Flex
columnGap={ 3 }
rowGap={ 3 }
fontFamily="heading"
fontSize="lg"
fontWeight={ 500 }
alignItems="center"
w="100%"
flexWrap={{ base: 'wrap', lg: 'nowrap' }}
>
<EnsEntity <EnsEntity
name={ domainName } name={ domainName }
isLoading={ isLoading } isLoading={ isLoading }
noLink noLink
maxW={ infoQuery.data?.resolved_address ? '300px' : 'min-content' } maxW={{ lg: infoQuery.data?.resolved_address ? '300px' : 'min-content' }}
/> />
{ infoQuery.data?.resolved_address && ( { infoQuery.data?.resolved_address && (
<Flex alignItems="center" maxW="100%" columnGap={ 3 }>
<AddressEntity <AddressEntity
address={ infoQuery.data?.resolved_address } address={ infoQuery.data?.resolved_address }
isLoading={ isLoading } isLoading={ isLoading }
truncation={ isMobile ? 'constant' : 'dynamic' }
flexShrink={ 0 }
/> />
) }
{ infoQuery.data?.resolved_address && (
<Tooltip label="Lookup for related domain names"> <Tooltip label="Lookup for related domain names">
<LinkInternal <LinkInternal
flexShrink={ 0 } flexShrink={ 0 }
...@@ -73,6 +77,7 @@ const NameDomain = () => { ...@@ -73,6 +77,7 @@ const NameDomain = () => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/> <IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal> </LinkInternal>
</Tooltip> </Tooltip>
</Flex>
) } ) }
</Flex> </Flex>
); );
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import NetworkExplorers from './NetworkExplorers';
test('base view', async({ mount, page }) => {
const component = await mount(
<TestApp>
<NetworkExplorers type="tx" pathParam="0x123"/>
</TestApp>,
);
await component.getByText('2').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 300, height: 150 } });
});
import { Flex, Button, chakra, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Show, Hide } from '@chakra-ui/react'; import {
Image,
Button,
Popover,
PopoverTrigger,
PopoverBody,
PopoverContent,
Show,
Hide,
useColorModeValue,
chakra,
useDisclosure,
Grid,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { NetworkExplorer as TNetworkExplorer } from 'types/networks'; import type { NetworkExplorer as TNetworkExplorer } from 'types/networks';
...@@ -16,15 +29,24 @@ interface Props { ...@@ -16,15 +29,24 @@ interface Props {
const NetworkExplorers = ({ className, type, pathParam }: Props) => { const NetworkExplorers = ({ className, type, pathParam }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
const defaultIconColor = useColorModeValue('gray.400', 'gray.500');
const explorersLinks = React.useMemo(() => { const explorersLinks = React.useMemo(() => {
return config.UI.explorers.items return config.UI.explorers.items
.filter((explorer) => typeof explorer.paths[type] === 'string') .filter((explorer) => typeof explorer.paths[type] === 'string')
.map((explorer) => { .map((explorer) => {
const url = new URL(stripTrailingSlash(explorer.paths[type] || '') + '/' + pathParam, explorer.baseUrl); const url = new URL(stripTrailingSlash(explorer.paths[type] || '') + '/' + pathParam, explorer.baseUrl);
return <LinkExternal key={ explorer.baseUrl } href={ url.toString() }>{ explorer.title }</LinkExternal>; return (
<LinkExternal h="34px" key={ explorer.baseUrl } href={ url.toString() } alignItems="center" display="inline-flex" minW="120px">
{ explorer.logo ?
<Image boxSize={ 5 } mr={ 2 } src={ explorer.logo } alt={ `${ explorer.title } icon` }/> :
<IconSvg name="explorer" boxSize={ 5 } color={ defaultIconColor } mr={ 2 }/>
}
{ explorer.title }
</LinkExternal>
);
}); });
}, [ pathParam, type ]); }, [ pathParam, type, defaultIconColor ]);
if (explorersLinks.length === 0) { if (explorersLinks.length === 0) {
return null; return null;
...@@ -54,18 +76,18 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => { ...@@ -54,18 +76,18 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => {
</Hide> </Hide>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent w="240px"> <PopoverContent w="auto">
<PopoverBody > <PopoverBody >
<chakra.span color="text_secondary" fontSize="xs">Verify with other explorers</chakra.span> <chakra.span color="text_secondary" fontSize="xs">Verify with other explorers</chakra.span>
<Flex <Grid
alignItems="center" alignItems="center"
flexWrap="wrap" templateColumns={ explorersLinks.length > 1 ? 'auto auto' : '1fr' }
columnGap={ 6 } columnGap={ 4 }
rowGap={ 3 } rowGap={ 2 }
mt={ 3 } mt={ 3 }
> >
{ explorersLinks } { explorersLinks }
</Flex> </Grid>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
......
...@@ -61,7 +61,7 @@ const TokenTransferListItem = ({ ...@@ -61,7 +61,7 @@ const TokenTransferListItem = ({
<TxAdditionalInfo hash={ txHash } isMobile isLoading={ isLoading }/> <TxAdditionalInfo hash={ txHash } isMobile isLoading={ isLoading }/>
) } ) }
</Flex> </Flex>
{ 'token_id' in total && <NftEntity hash={ token.address } id={ total.token_id } isLoading={ isLoading }/> } { 'token_id' in total && total.token_id !== null && <NftEntity hash={ token.address } id={ total.token_id } isLoading={ isLoading }/> }
{ showTxInfo && txHash && ( { showTxInfo && txHash && (
<Flex justifyContent="space-between" alignItems="center" lineHeight="24px" width="100%"> <Flex justifyContent="space-between" alignItems="center" lineHeight="24px" width="100%">
<TxEntity <TxEntity
......
...@@ -65,7 +65,7 @@ const TokenTransferTableItem = ({ ...@@ -65,7 +65,7 @@ const TokenTransferTableItem = ({
</Flex> </Flex>
</Td> </Td>
<Td> <Td>
{ 'token_id' in total && <NftEntity hash={ token.address } id={ total.token_id } isLoading={ isLoading }/> } { 'token_id' in total && total.token_id !== null && <NftEntity hash={ token.address } id={ total.token_id } isLoading={ isLoading }/> }
</Td> </Td>
{ showTxInfo && txHash && ( { showTxInfo && txHash && (
<Td> <Td>
......
...@@ -23,7 +23,7 @@ const ZkEvmBatchEntityL2 = (props: BlockEntity.EntityProps) => { ...@@ -23,7 +23,7 @@ const ZkEvmBatchEntityL2 = (props: BlockEntity.EntityProps) => {
<BlockEntity.Icon { ...partsProps } name="txn_batches_slim"/> <BlockEntity.Icon { ...partsProps } name="txn_batches_slim"/>
<BlockEntity.Link <BlockEntity.Link
{ ...linkProps } { ...linkProps }
href={ route({ pathname: '/zkevm-l2-txn-batch/[number]', query: { number: props.number.toString() } }) } href={ route({ pathname: '/batches/[number]', query: { number: props.number.toString() } }) }
> >
<BlockEntity.Content { ...partsProps }/> <BlockEntity.Content { ...partsProps }/>
</BlockEntity.Link> </BlockEntity.Link>
......
...@@ -16,7 +16,7 @@ const LayoutHome = ({ children }: Props) => { ...@@ -16,7 +16,7 @@ const LayoutHome = ({ children }: Props) => {
<Layout.MainArea> <Layout.MainArea>
<Layout.SideBar/> <Layout.SideBar/>
<Layout.MainColumn <Layout.MainColumn
paddingTop={{ base: 6, lg: 9 }} paddingTop={{ base: 3, lg: 6 }}
> >
<HeaderAlert/> <HeaderAlert/>
<AppErrorBoundary> <AppErrorBoundary>
......
...@@ -14,7 +14,7 @@ const MainColumn = ({ children, className }: Props) => { ...@@ -14,7 +14,7 @@ const MainColumn = ({ children, className }: Props) => {
flexGrow={ 1 } flexGrow={ 1 }
w={{ base: '100%', lg: 'auto' }} w={{ base: '100%', lg: 'auto' }}
paddingX={{ base: 4, lg: 12 }} paddingX={{ base: 4, lg: 12 }}
paddingTop={{ base: `${ 32 + 60 }px`, lg: 9 }} // 32px is top padding of content area, 60px is search bar height paddingTop={{ base: `${ 12 + 52 }px`, lg: 6 }} // 12px is top padding of content area, 52px is search bar height
paddingBottom={ 10 } paddingBottom={ 10 }
> >
{ children } { children }
......
...@@ -9,7 +9,7 @@ interface Props { ...@@ -9,7 +9,7 @@ interface Props {
onClose: () => void; onClose: () => void;
} }
const NftHtmlWithFullscreen = ({ src, isOpen, onClose }: Props) => { const NftHtmlFullscreen = ({ src, isOpen, onClose }: Props) => {
return ( return (
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }> <NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }>
<chakra.iframe <chakra.iframe
...@@ -22,4 +22,4 @@ const NftHtmlWithFullscreen = ({ src, isOpen, onClose }: Props) => { ...@@ -22,4 +22,4 @@ const NftHtmlWithFullscreen = ({ src, isOpen, onClose }: Props) => {
); );
}; };
export default NftHtmlWithFullscreen; export default NftHtmlFullscreen;
import { chakra, useDisclosure } from '@chakra-ui/react';
import React from 'react';
import NftHtml from './NftHtml';
import NftMediaFullscreenModal from './NftMediaFullscreenModal';
interface Props {
src: string;
onLoad: () => void;
onError: () => void;
}
const NftHtmlWithFullscreen = ({ src, onLoad, onError }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<NftHtml src={ src } onLoad={ onLoad } onError={ onError } onClick={ onOpen }/>
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }>
<chakra.iframe
w="90vw"
h="90vh"
src={ src }
sandbox="allow-scripts"
onLoad={ onLoad }
onError={ onError }
/>
</NftMediaFullscreenModal>
</>
);
};
export default NftHtmlWithFullscreen;
...@@ -11,12 +11,29 @@ interface Props { ...@@ -11,12 +11,29 @@ interface Props {
onClose: () => void; onClose: () => void;
} }
const NftImageWithFullscreen = ({ src, isOpen, onClose }: Props) => { const NftImageFullscreen = ({ src, isOpen, onClose }: Props) => {
const imgRef = React.useRef<HTMLImageElement>(null);
const [ hasDimentions, setHasDimentions ] = React.useState<boolean>(true);
const checkWidth = React.useCallback(() => {
if (imgRef.current?.getBoundingClientRect().width === 0) {
setHasDimentions(false);
}
}, [ ]);
return ( return (
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }> <NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }>
<Image src={ src } alt="Token instance image" maxH="90vh" maxW="90vw"/> <Image
src={ src }
alt="Token instance image"
maxH="90vh"
maxW="90vw"
ref={ imgRef }
onLoad={ checkWidth }
sx={ hasDimentions ? {} : { width: '90vw', height: '90vh' } }
/>
</NftMediaFullscreenModal> </NftMediaFullscreenModal>
); );
}; };
export default NftImageWithFullscreen; export default NftImageFullscreen;
import {
Image,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import NftImage from './NftImage';
import NftMediaFullscreenModal from './NftMediaFullscreenModal';
interface Props {
src: string;
onLoad: () => void;
onError: () => void;
}
const NftImageWithFullscreen = ({ src, onLoad, onError }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<NftImage src={ src } onLoad={ onLoad } onError={ onError } onClick={ onOpen }/>
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }>
<Image src={ src } alt="Token instance image" maxH="90vh" maxW="90vw"/>
</NftMediaFullscreenModal>
</>
);
};
export default NftImageWithFullscreen;
...@@ -10,7 +10,7 @@ interface Props { ...@@ -10,7 +10,7 @@ interface Props {
onClose: () => void; onClose: () => void;
} }
const NftVideoWithFullscreen = ({ src, isOpen, onClose }: Props) => { const NftVideoFullscreen = ({ src, isOpen, onClose }: Props) => {
return ( return (
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }> <NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }>
<chakra.video <chakra.video
...@@ -23,4 +23,4 @@ const NftVideoWithFullscreen = ({ src, isOpen, onClose }: Props) => { ...@@ -23,4 +23,4 @@ const NftVideoWithFullscreen = ({ src, isOpen, onClose }: Props) => {
); );
}; };
export default NftVideoWithFullscreen; export default NftVideoFullscreen;
import {
chakra,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import NftMediaFullscreenModal from './NftMediaFullscreenModal';
import NftVideo from './NftVideo';
import { videoPlayProps } from './utils';
interface Props {
src: string;
onLoad: () => void;
onError: () => void;
}
const NftVideoWithFullscreen = ({ src, onLoad, onError }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<NftVideo src={ src } onLoad={ onLoad } onError={ onError } onClick={ onOpen }/>
<NftMediaFullscreenModal isOpen={ isOpen } onClose={ onClose }>
<chakra.video
{ ...videoPlayProps }
src={ src }
onCanPlayThrough={ onLoad }
onError={ onError }
maxH="90vh"
maxW="90vw"
/>
</NftMediaFullscreenModal>
</>
);
};
export default NftVideoWithFullscreen;
...@@ -11,6 +11,10 @@ import WalletMenuMobile from 'ui/snippets/walletMenu/WalletMenuMobile'; ...@@ -11,6 +11,10 @@ import WalletMenuMobile from 'ui/snippets/walletMenu/WalletMenuMobile';
import Burger from './Burger'; import Burger from './Burger';
const LOGO_IMAGE_PROPS = {
margin: '0 auto',
};
type Props = { type Props = {
isHomePage?: boolean; isHomePage?: boolean;
renderSearchBar?: () => React.ReactNode; renderSearchBar?: () => React.ReactNode;
...@@ -47,7 +51,7 @@ const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => { ...@@ -47,7 +51,7 @@ const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => {
boxShadow={ !inView && scrollDirection === 'down' ? 'md' : 'none' } boxShadow={ !inView && scrollDirection === 'down' ? 'md' : 'none' }
> >
<Burger/> <Burger/>
<NetworkLogo/> <NetworkLogo imageProps={ LOGO_IMAGE_PROPS }/>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
{ config.features.account.isEnabled ? <ProfileMenuMobile/> : <Box boxSize={ 10 }/> } { config.features.account.isEnabled ? <ProfileMenuMobile/> : <Box boxSize={ 10 }/> }
{ config.features.blockchainInteraction.isEnabled && <WalletMenuMobile/> } { config.features.blockchainInteraction.isEnabled && <WalletMenuMobile/> }
......
import type { StyleProps } from '@chakra-ui/react';
import { Box, Image, useColorModeValue, Skeleton } from '@chakra-ui/react'; import { Box, Image, useColorModeValue, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -9,9 +10,10 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -9,9 +10,10 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
isCollapsed?: boolean; isCollapsed?: boolean;
onClick?: (event: React.SyntheticEvent) => void; onClick?: (event: React.SyntheticEvent) => void;
imageProps?: StyleProps;
} }
const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall?: boolean }) => { const LogoFallback = ({ isCollapsed, isSmall, imageProps }: { isCollapsed?: boolean; isSmall?: boolean; imageProps?: StyleProps }) => {
const field = isSmall ? 'icon' : 'logo'; const field = isSmall ? 'icon' : 'logo';
const logoColor = useColorModeValue('blue.600', 'white'); const logoColor = useColorModeValue('blue.600', 'white');
...@@ -36,11 +38,12 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall ...@@ -36,11 +38,12 @@ const LogoFallback = ({ isCollapsed, isSmall }: { isCollapsed?: boolean; isSmall
height="100%" height="100%"
color={ logoColor } color={ logoColor }
display={ display } display={ display }
{ ...imageProps }
/> />
); );
}; };
const NetworkLogo = ({ isCollapsed, onClick }: Props) => { const NetworkLogo = ({ isCollapsed, onClick, imageProps }: Props) => {
const logoSrc = useColorModeValue(config.UI.sidebar.logo.default, config.UI.sidebar.logo.dark || config.UI.sidebar.logo.default); const logoSrc = useColorModeValue(config.UI.sidebar.logo.default, config.UI.sidebar.logo.dark || config.UI.sidebar.logo.default);
const iconSrc = useColorModeValue(config.UI.sidebar.icon.default, config.UI.sidebar.icon.dark || config.UI.sidebar.icon.default); const iconSrc = useColorModeValue(config.UI.sidebar.icon.default, config.UI.sidebar.icon.dark || config.UI.sidebar.icon.default);
...@@ -66,9 +69,10 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => { ...@@ -66,9 +69,10 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
h="100%" h="100%"
src={ logoSrc } src={ logoSrc }
alt={ `${ config.chain.name } network logo` } alt={ `${ config.chain.name } network logo` }
fallback={ <LogoFallback isCollapsed={ isCollapsed }/> } fallback={ <LogoFallback isCollapsed={ isCollapsed } imageProps={ imageProps }/> }
display={{ base: 'block', lg: isCollapsed === false ? 'block' : 'none', xl: isCollapsed ? 'none' : 'block' }} display={{ base: 'block', lg: isCollapsed === false ? 'block' : 'none', xl: isCollapsed ? 'none' : 'block' }}
style={ logoStyle } style={ logoStyle }
{ ...imageProps }
/> />
{ /* small logo */ } { /* small logo */ }
<Image <Image
...@@ -76,9 +80,10 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => { ...@@ -76,9 +80,10 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
h="100%" h="100%"
src={ iconSrc } src={ iconSrc }
alt={ `${ config.chain.name } network logo` } alt={ `${ config.chain.name } network logo` }
fallback={ <LogoFallback isCollapsed={ isCollapsed } isSmall/> } fallback={ <LogoFallback isCollapsed={ isCollapsed } imageProps={ imageProps } isSmall/> }
display={{ base: 'none', lg: isCollapsed === false ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }} display={{ base: 'none', lg: isCollapsed === false ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}
style={ iconStyle } style={ iconStyle }
{ ...imageProps }
/> />
</Box> </Box>
); );
......
...@@ -111,7 +111,7 @@ const SearchBar = ({ isHomepage }: Props) => { ...@@ -111,7 +111,7 @@ const SearchBar = ({ isHomepage }: Props) => {
autoFocus={ false } autoFocus={ false }
onClose={ onClose } onClose={ onClose }
placement="bottom-start" placement="bottom-start"
offset={ isMobile && !isHomepage ? [ 16, -12 ] : undefined } offset={ isMobile && !isHomepage ? [ 16, -4 ] : undefined }
isLazy isLazy
> >
<PopoverTrigger> <PopoverTrigger>
......
...@@ -77,7 +77,7 @@ const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHid ...@@ -77,7 +77,7 @@ const SearchBarInput = ({ onChange, onSubmit, isHomepage, onFocus, onBlur, onHid
zIndex={{ base: isHomepage ? 'auto' : '-1', lg: 'auto' }} zIndex={{ base: isHomepage ? 'auto' : '-1', lg: 'auto' }}
paddingX={{ base: isHomepage ? 0 : 4, lg: 0 }} paddingX={{ base: isHomepage ? 0 : 4, lg: 0 }}
paddingTop={{ base: isHomepage ? 0 : 1, lg: 0 }} paddingTop={{ base: isHomepage ? 0 : 1, lg: 0 }}
paddingBottom={{ base: isHomepage ? 0 : 4, lg: 0 }} paddingBottom={{ base: isHomepage ? 0 : 2, lg: 0 }}
boxShadow={ scrollDirection !== 'down' && isSticky ? 'md' : 'none' } boxShadow={ scrollDirection !== 'down' && isSticky ? 'md' : 'none' }
transform={{ base: isHomepage ? 'none' : transformMobile, lg: 'none' }} transform={{ base: isHomepage ? 'none' : transformMobile, lg: 'none' }}
transitionProperty="transform,box-shadow,background-color,color,border-color" transitionProperty="transform,box-shadow,background-color,color,border-color"
......
...@@ -87,7 +87,7 @@ const TokenTransferListItem = ({ ...@@ -87,7 +87,7 @@ const TokenTransferListItem = ({
) } ) }
</Grid> </Grid>
) } ) }
{ 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && ( { 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && total.token_id !== null && (
<NftEntity <NftEntity
hash={ token.address } hash={ token.address }
id={ total.token_id } id={ total.token_id }
......
...@@ -70,7 +70,7 @@ const TokenTransferTableItem = ({ ...@@ -70,7 +70,7 @@ const TokenTransferTableItem = ({
</Td> </Td>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && ( { (token.type === 'ERC-721' || token.type === 'ERC-1155') && (
<Td> <Td>
{ 'token_id' in total ? ( { 'token_id' in total && total.token_id !== null ? (
<NftEntity <NftEntity
hash={ token.address } hash={ token.address }
id={ total.token_id } id={ total.token_id }
......
...@@ -9,7 +9,7 @@ import TokenEntity from 'ui/shared/entities/token/TokenEntity'; ...@@ -9,7 +9,7 @@ import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props { interface Props {
token: TokenInfo; token: TokenInfo;
value: string; value: string;
tokenId: string; tokenId: string | null;
} }
const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => { const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => {
...@@ -26,6 +26,7 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => { ...@@ -26,6 +26,7 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => {
) : ( ) : (
<chakra.span color="text_secondary">for token ID</chakra.span> <chakra.span color="text_secondary">for token ID</chakra.span>
) } ) }
{ tokenId !== null ? (
<NftEntity <NftEntity
hash={ token.address } hash={ token.address }
id={ tokenId } id={ tokenId }
...@@ -35,6 +36,8 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => { ...@@ -35,6 +36,8 @@ const NftTokenTransferSnippet = ({ value, token, tokenId }: Props) => {
w="auto" w="auto"
flexShrink={ 0 } flexShrink={ 0 }
/> />
) : <chakra.span color="text_secondary"> N/A </chakra.span>
}
<chakra.span color="text_secondary">of</chakra.span> <chakra.span color="text_secondary">of</chakra.span>
<TokenEntity <TokenEntity
token={ token } token={ token }
......
...@@ -10,6 +10,7 @@ import dayjs from 'lib/date/dayjs'; ...@@ -10,6 +10,7 @@ import dayjs from 'lib/date/dayjs';
import hexToDecimal from 'lib/hexToDecimal'; import hexToDecimal from 'lib/hexToDecimal';
import { publicClient } from 'lib/web3/client'; import { publicClient } from 'lib/web3/client';
import { GET_BLOCK, GET_TRANSACTION, GET_TRANSACTION_RECEIPT, GET_TRANSACTION_CONFIRMATIONS } from 'stubs/RPC'; import { GET_BLOCK, GET_TRANSACTION, GET_TRANSACTION_RECEIPT, GET_TRANSACTION_CONFIRMATIONS } from 'stubs/RPC';
import { unknownAddress } from 'ui/shared/address/utils';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning'; import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
import TestnetWarning from 'ui/shared/alerts/TestnetWarning'; import TestnetWarning from 'ui/shared/alerts/TestnetWarning';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
...@@ -66,16 +67,6 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => { ...@@ -66,16 +67,6 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => {
})(); })();
const gasPrice = txReceipt?.effectiveGasPrice ?? tx.gasPrice; const gasPrice = txReceipt?.effectiveGasPrice ?? tx.gasPrice;
const unknownAddress = {
is_contract: false,
is_verified: false,
implementation_name: '',
name: '',
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
};
return { return {
from: { ...unknownAddress, hash: tx.from as string }, from: { ...unknownAddress, hash: tx.from as string },
...@@ -86,7 +77,7 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => { ...@@ -86,7 +77,7 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => {
status, status,
block: tx.blockNumber ? Number(tx.blockNumber) : null, block: tx.blockNumber ? Number(tx.blockNumber) : null,
value: tx.value.toString(), value: tx.value.toString(),
gas_price: txReceipt?.effectiveGasPrice.toString() ?? tx.gasPrice?.toString() ?? null, gas_price: gasPrice?.toString() ?? null,
base_fee_per_gas: block?.baseFeePerGas?.toString() ?? null, base_fee_per_gas: block?.baseFeePerGas?.toString() ?? null,
max_fee_per_gas: tx.maxFeePerGas?.toString() ?? null, max_fee_per_gas: tx.maxFeePerGas?.toString() ?? null,
max_priority_fee_per_gas: tx.maxPriorityFeePerGas?.toString() ?? null, max_priority_fee_per_gas: tx.maxPriorityFeePerGas?.toString() ?? null,
......
import { Skeleton, Flex, Text, chakra } from '@chakra-ui/react'; import { Skeleton, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TxInterpretationSummary, TxInterpretationVariable } from 'types/api/txInterpretation'; import type {
TxInterpretationSummary,
TxInterpretationVariable,
TxInterpretationVariableString,
} from 'types/api/txInterpretation';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import { extractVariables, getStringChunks, NATIVE_COIN_SYMBOL_VAR_NAME } from './utils'; import { extractVariables, getStringChunks, fillStringVariables, NATIVE_COIN_SYMBOL_VAR_NAME } from './utils';
type Props = { type Props = {
summary?: TxInterpretationSummary; summary?: TxInterpretationSummary;
...@@ -19,7 +25,9 @@ type Props = { ...@@ -19,7 +25,9 @@ type Props = {
className?: string; className?: string;
} }
const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretationVariable }) => { type NonStringTxInterpretationVariable = Exclude<TxInterpretationVariable, TxInterpretationVariableString>
const TxInterpretationElementByType = ({ variable }: { variable?: NonStringTxInterpretationVariable }) => {
const onAddressClick = React.useCallback(() => { const onAddressClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' }); mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' });
}, []); }, []);
...@@ -28,6 +36,10 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati ...@@ -28,6 +36,10 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Token click' }); mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Token click' });
}, []); }, []);
const onDomainClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Domain click' });
}, []);
if (!variable) { if (!variable) {
return null; return null;
} }
...@@ -36,28 +48,47 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati ...@@ -36,28 +48,47 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati
switch (type) { switch (type) {
case 'address': { case 'address': {
return ( return (
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<AddressEntity <AddressEntity
address={ value } address={ value }
truncation="constant" truncation="constant"
sx={{ ':not(:first-child)': { marginLeft: 1 } }}
whiteSpace="initial"
onClick={ onAddressClick } onClick={ onAddressClick }
whiteSpace="initial"
/> />
</chakra.span>
); );
} }
case 'token': case 'token':
return ( return (
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<TokenEntity <TokenEntity
token={ value } token={ value }
onlySymbol onlySymbol
noCopy noCopy
width="fit-content" width="fit-content"
sx={{ ':not(:first-child)': { marginLeft: 1 } }} _notFirst={{ marginLeft: 1 }}
mr={ 2 } mr={ 2 }
whiteSpace="initial" whiteSpace="initial"
onClick={ onTokenClick } onClick={ onTokenClick }
/> />
</chakra.span>
);
case 'domain': {
if (config.features.nameService.isEnabled) {
return (
<chakra.span display="inline-block" verticalAlign="top" _notFirst={{ marginLeft: 1 }}>
<EnsEntity
name={ value }
width="fit-content"
_notFirst={{ marginLeft: 1 }}
whiteSpace="initial"
onClick={ onDomainClick }
/>
</chakra.span>
); );
}
return <chakra.span color="text_secondary" whiteSpace="pre">{ value + ' ' }</chakra.span>;
}
case 'currency': { case 'currency': {
let numberString = ''; let numberString = '';
if (BigNumber(value).isLessThan(0.1)) { if (BigNumber(value).isLessThan(0.1)) {
...@@ -69,14 +100,10 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati ...@@ -69,14 +100,10 @@ const TxInterpretationElementByType = ({ variable }: { variable?: TxInterpretati
} else { } else {
numberString = BigNumber(value).dividedBy(1000000).toFormat(2) + 'M'; numberString = BigNumber(value).dividedBy(1000000).toFormat(2) + 'M';
} }
return <Text>{ numberString + ' ' }</Text>; return <chakra.span>{ numberString + ' ' }</chakra.span>;
} }
case 'timestamp': case 'timestamp': {
// timestamp is in unix format return <chakra.span color="text_secondary" whiteSpace="pre">{ dayjs(Number(value) * 1000).format('MMM DD YYYY') }</chakra.span>;
return <Text color="text_secondary">{ dayjs(Number(value) * 1000).format('llll') + ' ' }</Text>;
case 'string':
default: {
return <Text color="text_secondary">{ value.toString() + ' ' }</Text>;
} }
} }
}; };
...@@ -89,23 +116,24 @@ const TxInterpretation = ({ summary, isLoading, className }: Props) => { ...@@ -89,23 +116,24 @@ const TxInterpretation = ({ summary, isLoading, className }: Props) => {
const template = summary.summary_template; const template = summary.summary_template;
const variables = summary.summary_template_variables; const variables = summary.summary_template_variables;
const variablesNames = extractVariables(template); const intermediateResult = fillStringVariables(template, variables);
const chunks = getStringChunks(template); const variablesNames = extractVariables(intermediateResult);
const chunks = getStringChunks(intermediateResult);
return ( return (
<Skeleton display="flex" flexWrap="wrap" alignItems="center" isLoaded={ !isLoading } className={ className }> <Skeleton isLoaded={ !isLoading } className={ className } fontWeight={ 500 } whiteSpace="pre-wrap" >
<IconSvg name="lightning" boxSize={ 5 } color="text_secondary" mr={ 2 }/> <IconSvg name="lightning" boxSize={ 5 } color="text_secondary" mr={ 2 } verticalAlign="text-top"/>
{ chunks.map((chunk, index) => { { chunks.map((chunk, index) => {
return ( return (
<Flex whiteSpace="pre" key={ chunk + index } fontWeight={ 500 }> <chakra.span key={ chunk + index }>
<Text color="text_secondary">{ chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') }</Text> <chakra.span color="text_secondary">{ chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') }</chakra.span>
{ index < variablesNames.length && ( { index < variablesNames.length && (
variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME ? variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME ?
<Text>{ currencyUnits.ether + ' ' }</Text> : <chakra.span>{ currencyUnits.ether + ' ' }</chakra.span> :
<TxInterpretationElementByType variable={ variables[variablesNames[index]] }/> <TxInterpretationElementByType variable={ variables[variablesNames[index]] as NonStringTxInterpretationVariable }/>
) } ) }
</Flex> </chakra.span>
); );
}) } }) }
</Skeleton> </Skeleton>
......
// we use that regex as a separator when splitting template and dont want to capture variables // we use that regex as a separator when splitting template and dont want to capture variables
import type { TxInterpretationVariable } from 'types/api/txInterpretation';
// eslint-disable-next-line regexp/no-useless-non-capturing-group // eslint-disable-next-line regexp/no-useless-non-capturing-group
export const VAR_REGEXP = /\{(?:[^}]+)\}/g; export const VAR_REGEXP = /\{(?:[^}]+)\}/g;
...@@ -16,3 +19,16 @@ export function extractVariables(templateString: string) { ...@@ -16,3 +19,16 @@ export function extractVariables(templateString: string) {
export function getStringChunks(template: string) { export function getStringChunks(template: string) {
return template.split(VAR_REGEXP); return template.split(VAR_REGEXP);
} }
export function fillStringVariables(template: string, variables: Record<string, TxInterpretationVariable>) {
const variablesNames = extractVariables(template);
let result = template;
variablesNames.forEach(name => {
if (variables[name] && variables[name].type === 'string') {
result = result.replace(`{${ name }}`, variables[name].value as string);
}
});
return result;
}
import { Flex, Link, useBoolean } from '@chakra-ui/react'; import { Flex, Text, Link, useBoolean } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import NftEntity from 'ui/shared/entities/nft/NftEntity'; import NftEntity from 'ui/shared/entities/nft/NftEntity';
interface Props { interface Props {
items: Array<{total: { token_id: string} }>; items: Array<{total: { token_id: string | null} }>;
tokenAddress: string; tokenAddress: string;
isLoading?: boolean; isLoading?: boolean;
} }
...@@ -14,14 +14,20 @@ const TxStateTokenIdList = ({ items, tokenAddress, isLoading }: Props) => { ...@@ -14,14 +14,20 @@ const TxStateTokenIdList = ({ items, tokenAddress, isLoading }: Props) => {
return ( return (
<Flex flexDir="column" rowGap={ 2 }> <Flex flexDir="column" rowGap={ 2 }>
{ items.slice(0, isCut ? 3 : items.length).map((item, index) => ( { items.slice(0, isCut ? 3 : items.length).map((item, index) => {
if (item.total.token_id !== null) {
return (
<NftEntity <NftEntity
key={ index } key={ index }
hash={ tokenAddress } hash={ tokenAddress }
id={ item.total.token_id } id={ item.total.token_id }
isLoading={ isLoading } isLoading={ isLoading }
/> />
)) } );
} else {
return <Text key={ index } color="text_secondary">N/A</Text>;
}
}) }
{ items.length > 3 && ( { items.length > 3 && (
<Link <Link
fontWeight={ 400 } fontWeight={ 400 }
......
...@@ -26,7 +26,7 @@ const UserOpsContent = ({ query, showTx = true, showSender = true }: Props) => { ...@@ -26,7 +26,7 @@ const UserOpsContent = ({ query, showTx = true, showSender = true }: Props) => {
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<UserOpsTable <UserOpsTable
items={ query.data.items } items={ query.data.items }
top={ query.pagination.isVisible ? 0 : 80 } top={ query.pagination.isVisible ? 80 : 0 }
isLoading={ query.isPlaceholderData } isLoading={ query.isPlaceholderData }
showTx={ showTx } showTx={ showTx }
showSender={ showSender } showSender={ showSender }
......
...@@ -38,7 +38,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => { ...@@ -38,7 +38,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
const increment = direction === 'next' ? +1 : -1; const increment = direction === 'next' ? +1 : -1;
const nextId = String(data.number + increment); const nextId = String(data.number + increment);
router.push({ pathname: '/zkevm-l2-txn-batch/[number]', query: { number: nextId } }, undefined); router.push({ pathname: '/batches/[number]', query: { number: nextId } }, undefined);
}, [ data, router ]); }, [ data, router ]);
if (isError) { if (isError) {
...@@ -105,7 +105,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => { ...@@ -105,7 +105,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
> >
<Skeleton isLoaded={ !isPlaceholderData }> <Skeleton isLoaded={ !isPlaceholderData }>
<LinkInternal href={ route({ pathname: '/zkevm-l2-txn-batch/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }> <LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }>
{ data.transactions.length } transaction{ data.transactions.length === 1 ? '' : 's' } { data.transactions.length } transaction{ data.transactions.length === 1 ? '' : 's' }
</LinkInternal> </LinkInternal>
</Skeleton> </Skeleton>
......
...@@ -51,7 +51,7 @@ const ZkEvmTxnBatchesListItem = ({ item, isLoading }: Props) => { ...@@ -51,7 +51,7 @@ const ZkEvmTxnBatchesListItem = ({ item, isLoading }: Props) => {
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn count</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Txn count</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value> <ListItemMobileGrid.Value>
<LinkInternal <LinkInternal
href={ route({ pathname: '/zkevm-l2-txn-batch/[number]', query: { number: item.number.toString(), tab: 'txs' } }) } href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) }
isLoading={ isLoading } isLoading={ isLoading }
fontWeight={ 600 } fontWeight={ 600 }
> >
......
...@@ -44,7 +44,7 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => { ...@@ -44,7 +44,7 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<LinkInternal <LinkInternal
href={ route({ pathname: '/zkevm-l2-txn-batch/[number]', query: { number: item.number.toString(), tab: 'txs' } }) } href={ route({ pathname: '/batches/[number]', query: { number: item.number.toString(), tab: 'txs' } }) }
isLoading={ isLoading } isLoading={ isLoading }
> >
<Skeleton isLoaded={ !isLoading } minW="40px" my={ 1 }> <Skeleton isLoaded={ !isLoading } minW="40px" my={ 1 }>
......
...@@ -10365,6 +10365,11 @@ focus-lock@^0.11.6: ...@@ -10365,6 +10365,11 @@ focus-lock@^0.11.6:
dependencies: dependencies:
tslib "^2.0.3" tslib "^2.0.3"
focus-visible@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.2.0.tgz#3a9e41fccf587bd25dcc2ef045508284f0a4d6b3"
integrity sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==
follow-redirects@^1.15.0: follow-redirects@^1.15.0:
version "1.15.2" version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment