Commit 09df0fd1 authored by Max Alekseenko's avatar Max Alekseenko

Merge branch 'main' into rework-marketplace-app-header

parents fb7aff5b d7e984b4
......@@ -2,6 +2,25 @@ name: Deploy review environment
on:
workflow_dispatch:
inputs:
envs_preset:
description: ENVs preset
required: false
default: ""
type: choice
options:
- none
- gnosis
- eth
- eth_sepolia
- eth_goerli
- optimism
- optimism_sepolia
- polygon
- rootstock
- stability
- zkevm
- zksync
jobs:
make_slug:
......@@ -23,6 +42,7 @@ jobs:
uses: './.github/workflows/publish-image.yml'
with:
tags: ghcr.io/blockscout/frontend:review-${{ needs.make_slug.outputs.REF_SLUG }}
build_args: ENVS_PRESET=${{ inputs.envs_preset }}
secrets: inherit
deploy_review:
......
......@@ -7,6 +7,10 @@ on:
description: Image tags
required: false
type: string
build_args:
description: Build-time variables
required: false
type: string
platforms:
description: Image platforms (you can specify multiple platforms separated by comma)
required: false
......@@ -18,6 +22,10 @@ on:
description: Image tags
required: false
type: string
build_args:
description: Build-time variables
required: false
type: string
platforms:
description: Image platforms (you can specify multiple platforms separated by comma)
required: false
......@@ -72,4 +80,5 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_COMMIT_SHA=${{ env.SHORT_SHA }}
GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }}
\ No newline at end of file
GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }}
${{ inputs.build_args }}
\ No newline at end of file
......@@ -338,17 +338,18 @@
"options": [
"main",
"main.L2",
"localhost",
"gnosis",
"eth",
"eth_goerli",
"sepolia",
"eth_sepolia",
"optimism",
"optimism_sepolia",
"polygon",
"zkevm",
"zksync",
"gnosis",
"rootstock",
"stability",
"poa_core",
"localhost",
"zkevm",
"zksync",
],
"default": "main"
},
......
......@@ -119,6 +119,11 @@ RUN ["chmod", "-R", "777", "./public"]
COPY --from=builder /app/.env.registry .
COPY --from=builder /app/.env .
# Copy ENVs presets
ARG ENVS_PRESET
ENV ENVS_PRESET=$ENVS_PRESET
COPY ./configs/envs ./configs/envs
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
......
......@@ -12,8 +12,8 @@ const chain = Object.freeze({
symbol: getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL'),
decimals: Number(getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS')) || DEFAULT_CURRENCY_DECIMALS,
},
governanceToken: {
symbol: getEnvValue('NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL'),
secondaryCoin: {
symbol: getEnvValue('NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL'),
},
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'),
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
......
......@@ -8,6 +8,7 @@ const meta = Object.freeze({
og: {
description: getEnvValue('NEXT_PUBLIC_OG_DESCRIPTION') || '',
imageUrl: app.baseUrl + (getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl),
enhancedDataEnabled: getEnvValue('NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED') === 'true',
},
});
......
......@@ -22,21 +22,21 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap']
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth.json
## footer
##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'}]
## misc
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'}} ]"
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
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_HAS_BEACON_CHAIN=true
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
......
......@@ -44,7 +44,7 @@ NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://sepolia.e
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_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
......
......@@ -13,6 +13,7 @@ NEXT_PUBLIC_NETWORK_ID=100
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=xDAI
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=xDAI
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.gnosischain.com
......@@ -22,9 +23,9 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgb(46, 74, 60)"
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR="rgb(255, 255, 255)"
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','secondary_coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgb(46,74,60)
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255,255,255)
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/gnosis-chain-mainnet.json
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/gnosis.svg
......@@ -42,6 +43,7 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace/gnosis-chain.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrmiO9mDGJoPNmJe
NEXT_PUBLIC_STATS_API_HOST=https://stats-gnosis-mainnet.k8s-prod-1.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask']
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
......
......@@ -13,7 +13,6 @@ NEXT_PUBLIC_NETWORK_ID=5
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_IS_TESTNET=true
......
# Set of ENVs for Optimism (dev only)
# https://optimism.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME=OP Mainnet
NEXT_PUBLIC_NETWORK_SHORT_NAME=OP
NEXT_PUBLIC_NETWORK_ID=10
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=OP
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://mainnet.optimism.io
# api configuration
NEXT_PUBLIC_API_HOST=optimism.blockscout.com
NEXT_PUBLIC_API_PORT=80
NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap','secondary_coin_price']
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255,255,255)
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(90deg,rgb(232,52,53)0%,rgb(139,28,232)100%)
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/optimism-mainnet.json
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://optimism.drpc.org?ref=559183','text':'Public RPC'}]
## footer
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json
## views
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'}]
## misc
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/optimism-mainnet.png
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Tenderly','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/optimistic'}},{'title':'3xpl','baseUrl':'https://3xpl.com/','paths':{'tx':'/optimism/transaction','address':'/optimism/address'}}]
# app features
NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-mainnet.k8s-prod-1.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
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
# Set of ENVs for zkevm (dev only)
# https://eth.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME='OP Goerli'
NEXT_PUBLIC_NETWORK_SHORT_NAME='OP Goerli'
NEXT_PUBLIC_NETWORK_ID=420
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://goerli.optimism.io
# api configuration
NEXT_PUBLIC_API_HOST=optimism-goerli.blockscout.com
NEXT_PUBLIC_API_PORT=80
NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/polygon-mainnet.json
## footer
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
# app features
NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
# NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
# 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-goerli.blockscout.com/
\ No newline at end of file
# Set of ENVs for Optimism (dev only)
# https://optimism.blockscout.com/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME=OP Sepolia
NEXT_PUBLIC_NETWORK_SHORT_NAME=OP Sepolia
NEXT_PUBLIC_NETWORK_ID=11155420
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=OP
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia.optimism.io
# api configuration
NEXT_PUBLIC_API_HOST=optimism-sepolia.blockscout.com
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255,255,255)
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(90deg,rgb(232,52,53)0%,rgb(139,28,232)100%)
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/optimism-sepolia.json
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://optimism.drpc.org?ref=559183','text':'Public RPC'}]
## footer
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json
## views
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'}]
## misc
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/optimism-mainnet.png
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Tenderly','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/optimistic-sepolia'}}]
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=<p>Build faster with the <a href='https://console.optimism.io' target='_blank'>Superchain Dev Console</a>: Get testnet ETH and tools to help you build,launch,and grow your app on the Superchain</p>
# app features
NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x59d26836041ab35169bdce431d68d070b7b8acb589fa52e126e6c828b6ece5e9
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-sepolia.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_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
# 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-sepolia.blockscout.com/
# Set of ENVs for POA network explorer
# https://blockscout.com/poa/core/
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
# blockchain parameters
NEXT_PUBLIC_NETWORK_NAME=POA
NEXT_PUBLIC_NETWORK_SHORT_NAME=POA
NEXT_PUBLIC_NETWORK_ID=99
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=POA
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
# api configuration
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/poa/core
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='no-repeat bottom 20% right 0px/100% url(https://neon-labs.org/images/index/banner.jpg)'
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=\#DCFE76
## sidebar
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/poa.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/poa.svg
## footer
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address','block':'/ethereum/poa/core/block'}}]
# app features
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_NETWORK_RPC_URL=https://core.poa.network
......@@ -23,8 +23,8 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
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)"
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)
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/polygon-mainnet.json
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/polygon.svg
......@@ -35,9 +35,7 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c
# app features
NEXT_PUBLIC_APP_ENV=development
# NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x97fa753626b8d44011d0b9f9a947c735f20b6e895efdee49d7cda76a50001017
NEXT_PUBLIC_HAS_BEACON_CHAIN=false
# NEXT_PUBLIC_STATS_API_HOST=https://stats-rsk-testnet.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask']
......
......@@ -23,7 +23,7 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgb(255, 145, 0)"
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgb(255,145,0)
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/rsk-testnet.json
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/rootstock.svg
......
......@@ -24,9 +24,8 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgb(255, 145, 0)"
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgba(46, 51, 81, 1)"
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR="rgba(122, 235, 246, 1)"
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(46,51,81,1)
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(122,235,246,1)
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/rsk-testnet.json
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/stability.svg
......@@ -35,10 +34,10 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/stability-short-dark.svg
## footer
## views
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS="['top_accounts']"
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS="['value','fee_currency','gas_price','gas_fees','burnt_fees']"
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS="['fee_per_gas']"
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS="['burnt_fees','total_reward']"
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts']
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','gas_fees','burnt_fees']
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS=['fee_per_gas']
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward']
## misc
# app features
......@@ -47,14 +46,16 @@ NEXT_PUBLIC_HAS_BEACON_CHAIN=false
NEXT_PUBLIC_STATS_API_HOST=https://stats-stability-testnet.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_CONTRACT_CODE_IDES="[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
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/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com/
NEXT_PUBLIC_GAS_TRACKER_ENABLED=false
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE='stability'
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability
#meta
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/stability.png
......@@ -29,8 +29,8 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
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)'
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
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
......@@ -39,7 +39,6 @@ NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
# NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com
......
......@@ -31,8 +31,8 @@ NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zksync-dark.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zksync-short.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zksync-short-dark.svg
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(53, 103, 246, 1)'
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(255, 255, 255, 1)'
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(53,103,246,1)
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255,255,255,1)
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://zksync.drpc.org?ref=559183','text':'Public RPC'}]
## footer
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/zksync.json
......@@ -46,9 +46,8 @@ NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x79c7802ccdf3be5a49c47cc751aad351b0027e8275f6f54878eda50ee559a648
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
# NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_LOGOUT_URL=https://zksync.us.auth0.com/v2/logout
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
......
#!/bin/bash
export_envs_from_preset() {
if [ -z "$ENVS_PRESET" ]; then
return
fi
if [ "$ENVS_PRESET" = "none" ]; then
return
fi
local preset_file="./configs/envs/.env.$ENVS_PRESET"
if [ ! -f "$preset_file" ]; then
return
fi
local blacklist=(
"NEXT_PUBLIC_APP_PROTOCOL"
"NEXT_PUBLIC_APP_HOST"
"NEXT_PUBLIC_APP_PORT"
"NEXT_PUBLIC_APP_ENV"
)
while IFS='=' read -r name value; do
name="${name#"${name%%[![:space:]]*}"}" # Trim leading whitespace
if [[ -n $name && $name == "NEXT_PUBLIC_"* && ! "${blacklist[*]}" =~ "$name" ]]; then
export "$name"="$value"
fi
done < <(grep "^[^#;]" "$preset_file")
}
# If there is a preset, load the environment variables from the its file
export_envs_from_preset
# Download external assets
./download_assets.sh ./public/assets
......
......@@ -25,6 +25,7 @@ import type { ValidatorsChainType } from '../../../types/client/validators';
import type { WalletType } from '../../../types/client/wallets';
import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
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 type { AddressViewId } from '../../../types/views/address';
......@@ -481,7 +482,7 @@ const schema = yup
NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(),
NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string<NetworkVerificationType>().oneOf([ 'validation', 'mining' ]),
NEXT_PUBLIC_IS_TESTNET: yup.boolean(),
......@@ -498,7 +499,7 @@ const schema = yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string<ChainIndicatorId>().oneOf([ 'daily_txs', 'coin_price', 'market_cap', 'tvl' ])),
.of(yup.string<ChainIndicatorId>().oneOf(CHAIN_INDICATOR_IDS)),
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(),
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(),
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME: yup.boolean(),
......@@ -601,6 +602,7 @@ const schema = yup
NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(),
NEXT_PUBLIC_OG_DESCRIPTION: yup.string(),
NEXT_PUBLIC_OG_IMAGE_URL: yup.string().test(urlTest),
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: yup.boolean(),
NEXT_PUBLIC_SAFE_TX_SERVICE_URL: yup.string().test(urlTest),
NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(),
NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(),
......
......@@ -30,7 +30,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Explorer','baseUrl':'https://example.com/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL=gETH
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO
NEXT_PUBLIC_NETWORK_ICON=https://example.com/icon.png
NEXT_PUBLIC_NETWORK_ICON_DARK=https://example.com/icon.png
NEXT_PUBLIC_NETWORK_LOGO=https://example.com/logo.png
......@@ -40,6 +40,7 @@ NEXT_PUBLIC_NETWORK_SHORT_NAME=Test
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_OG_DESCRIPTION='Hello world!'
NEXT_PUBLIC_OG_IMAGE_URL=https://example.com/image.png
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://blockscout.com','text':'Blockscout'}]
NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE=true
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global
......
......@@ -85,7 +85,7 @@ frontend:
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_HAS_USER_OPS: true
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: noves
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
NEXT_PUBLIC_AD_BANNER_PROVIDER: getit
......@@ -93,7 +93,7 @@ frontend:
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: "{ \"id\": \"632019\", \"width\": \"728\", \"height\": \"90\" }"
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }"
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
PROMETHEUS_METRICS_ENABLED: true
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true
envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
......@@ -6,4 +6,5 @@
| NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK | `boolean` | Set to true for zkevm L2 solutions | Required | - | `true` | v1.24.0 | Replaced by NEXT_PUBLIC_ROLLUP_TYPE |
| 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 |
\ No newline at end of file
| 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
......@@ -73,6 +73,8 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
## Blockchain parameters
*Note!* The `NEXT_PUBLIC_NETWORK_CURRENCY` variables represent the blockchain's native token used for paying transaction fees. `NEXT_PUBLIC_NETWORK_SECONDARY_COIN` variables refer to tokens like protocol-specific tokens (e.g., OP token on Optimism chain) or governance tokens (e.g., GNO on Gnosis chain).
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` |
......@@ -83,7 +85,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of network currency subdenomination | - | `wei` | `duck` |
| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` |
| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` |
| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` |
| 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_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` |
......@@ -107,7 +109,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` |
| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'secondary_coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `white` | `\#DCFE76` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `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)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` |
| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` |
......@@ -176,6 +178,7 @@ Settings for meta tags and OG tags
| NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE | `boolean` | Set to `true` to promote Blockscout in meta and OG titles | - | `true` | `true` |
| NEXT_PUBLIC_OG_DESCRIPTION | `string` | Custom OG 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.` |
| NEXT_PUBLIC_OG_IMAGE_URL | `string` | OG image url. Minimum image size is 200 x 20 pixels (recommended: 1200 x 600); maximum supported file size is 8 MB; 2:1 aspect ratio; supported formats: image/jpeg, image/gif, image/png | - | `static/og_placeholder.png` | `https://placekitten.com/1200/600` |
| NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED | `boolean` | Set to `true` to populate OG tags (title, description) with API data for social preview robot requests | - | `false` | `true` |
&nbsp;
......
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 4.167c0-.92.746-1.667 1.667-1.667H8c.92 0 1.667.746 1.667 1.667V7.5c0 .92-.747 1.667-1.667 1.667H4.667C3.747 9.167 3 8.42 3 7.5V4.167Zm1.667.5a.5.5 0 0 1 .5-.5H7.5a.5.5 0 0 1 .5.5V7a.5.5 0 0 1-.5.5H5.167a.5.5 0 0 1-.5-.5V4.667ZM3 12.5c0-.92.746-1.667 1.667-1.667H8c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.747 1.667-1.667 1.667H4.667C3.747 17.5 3 16.754 3 15.833V12.5Zm1.667.5a.5.5 0 0 1 .5-.5H7.5a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H5.167a.5.5 0 0 1-.5-.5V13ZM13 2.5c-.92 0-1.667.746-1.667 1.667V7.5c0 .92.746 1.667 1.667 1.667h3.333C17.253 9.167 18 8.42 18 7.5V4.167c0-.92-.746-1.667-1.667-1.667H13Zm3.333 2.167a.5.5 0 0 0-.5-.5H13.5a.5.5 0 0 0-.5.5V7a.5.5 0 0 0 .5.5h2.333a.5.5 0 0 0 .5-.5V4.667ZM11.333 12.5c0-.92.746-1.667 1.667-1.667h3.333c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.746 1.667-1.667 1.667H13c-.92 0-1.667-.746-1.667-1.667V12.5ZM13 13a.5.5 0 0 1 .5-.5h2.333a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H13.5a.5.5 0 0 1-.5-.5V13Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 4.167c0-.92.746-1.667 1.667-1.667H8c.92 0 1.667.746 1.667 1.667V7.5c0 .92-.747 1.667-1.667 1.667H4.667C3.747 9.167 3 8.42 3 7.5V4.167Zm1.667.5a.5.5 0 0 1 .5-.5H7.5a.5.5 0 0 1 .5.5V7a.5.5 0 0 1-.5.5H5.167a.5.5 0 0 1-.5-.5V4.667ZM3 12.5c0-.92.746-1.667 1.667-1.667H8c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.747 1.667-1.667 1.667H4.667C3.747 17.5 3 16.754 3 15.833V12.5Zm1.667.5a.5.5 0 0 1 .5-.5H7.5a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H5.167a.5.5 0 0 1-.5-.5V13ZM13 2.5c-.92 0-1.667.746-1.667 1.667V7.5c0 .92.746 1.667 1.667 1.667h3.333C17.253 9.167 18 8.42 18 7.5V4.167c0-.92-.746-1.667-1.667-1.667H13Zm3.333 2.167a.5.5 0 0 0-.5-.5H13.5a.5.5 0 0 0-.5.5V7a.5.5 0 0 0 .5.5h2.333a.5.5 0 0 0 .5-.5V4.667Zm-5 7.833c0-.92.746-1.667 1.667-1.667h3.333c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.746 1.667-1.667 1.667H13c-.92 0-1.667-.746-1.667-1.667V12.5ZM13 13a.5.5 0 0 1 .5-.5h2.333a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H13.5a.5.5 0 0 1-.5-.5V13Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 26 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="26" height="12" rx="2" fill="#F56565"/>
<path d="M3.028 9V1.727h1.061V4.43h.064c.062-.114.15-.245.267-.394.116-.15.277-.28.483-.391.205-.114.478-.17.816-.17.44 0 .834.11 1.18.333.345.223.616.544.812.963.2.419.299.923.299 1.512 0 .59-.098 1.095-.295 1.517a2.3 2.3 0 0 1-.81.97 2.097 2.097 0 0 1-1.175.337c-.332 0-.603-.056-.813-.167a1.54 1.54 0 0 1-.49-.391 2.957 2.957 0 0 1-.274-.398h-.089V9H3.028Zm1.04-2.727c0 .383.056.72.167 1.008.111.29.272.515.483.679.21.16.469.241.774.241.317 0 .582-.084.795-.252.214-.17.375-.401.483-.693.112-.29.167-.619.167-.983 0-.36-.054-.683-.163-.97a1.484 1.484 0 0 0-.483-.678c-.213-.166-.48-.249-.799-.249-.308 0-.568.08-.781.238-.21.159-.37.38-.48.664a2.77 2.77 0 0 0-.163.995Zm7.485 2.837c-.538 0-1-.115-1.389-.344a2.336 2.336 0 0 1-.894-.977c-.209-.421-.313-.915-.313-1.48 0-.56.104-1.052.313-1.478.21-.426.504-.759.88-.998.379-.239.822-.359 1.328-.359.308 0 .606.051.895.153.29.102.548.262.778.48.23.217.41.5.543.848.133.346.2.766.2 1.26v.377H9.556v-.795h3.296c0-.28-.057-.527-.17-.742a1.29 1.29 0 0 0-1.198-.703c-.298 0-.558.073-.78.22-.221.144-.391.334-.512.568a1.641 1.641 0 0 0-.178.756v.622c0 .364.064.674.192.93.13.256.311.451.543.586.232.133.503.199.814.199.2 0 .384-.028.55-.085a1.143 1.143 0 0 0 .707-.692l1.005.18a1.82 1.82 0 0 1-.434.778 2.1 2.1 0 0 1-.777.515 2.91 2.91 0 0 1-1.062.181Zm6.064-5.565v.853h-2.979v-.853h2.98Zm-2.18-1.306h1.062v5.16c0 .205.03.36.092.465.062.101.14.171.238.21.1.035.207.052.323.052.085 0 .16-.005.224-.017l.149-.029.192.877a2.08 2.08 0 0 1-.689.114 1.87 1.87 0 0 1-.781-.15 1.34 1.34 0 0 1-.586-.482c-.15-.218-.224-.491-.224-.82v-5.38Zm4.942 6.882c-.345 0-.658-.064-.937-.192a1.58 1.58 0 0 1-.664-.565c-.161-.246-.242-.548-.242-.905 0-.308.06-.561.178-.76a1.31 1.31 0 0 1 .48-.472c.2-.116.425-.204.674-.263.248-.06.502-.104.76-.135l.795-.092c.204-.027.352-.068.444-.125.092-.057.139-.149.139-.277V5.31c0-.31-.088-.55-.263-.72-.173-.171-.431-.256-.774-.256-.358 0-.64.08-.845.238-.204.156-.345.33-.423.522l-.998-.228a1.92 1.92 0 0 1 .519-.802c.23-.206.493-.355.791-.448.299-.094.612-.142.941-.142.218 0 .45.026.693.079.246.05.476.142.689.277.215.134.392.327.53.578.136.249.205.572.205.97V9h-1.037v-.746h-.043a1.512 1.512 0 0 1-.308.405 1.642 1.642 0 0 1-.53.33 2.053 2.053 0 0 1-.774.132Zm.231-.853c.294 0 .545-.058.753-.174.21-.116.37-.267.48-.454.11-.19.166-.392.166-.607V6.33a.552.552 0 0 1-.22.106 3.43 3.43 0 0 1-.366.082l-.401.06c-.13.017-.24.03-.327.043a2.63 2.63 0 0 0-.564.131.97.97 0 0 0-.405.266.665.665 0 0 0-.15.455c0 .263.098.462.292.597.194.132.441.198.742.198Z" fill="#fff"/>
<path d="M3.028 9V1.727h1.061V4.43h.064c.062-.114.15-.245.267-.394.116-.15.277-.28.483-.391.205-.114.478-.17.816-.17.44 0 .834.11 1.18.333.345.223.616.544.812.963.2.419.299.923.299 1.512 0 .59-.098 1.095-.295 1.517a2.3 2.3 0 0 1-.81.97 2.097 2.097 0 0 1-1.175.337c-.332 0-.603-.056-.813-.167a1.54 1.54 0 0 1-.49-.391 2.957 2.957 0 0 1-.274-.398h-.089V9H3.028Zm1.04-2.727c0 .383.056.72.167 1.008.111.29.272.515.483.679.21.16.469.241.774.241.317 0 .582-.084.795-.252.214-.17.375-.401.483-.693.112-.29.167-.619.167-.983 0-.36-.054-.683-.163-.97a1.484 1.484 0 0 0-.483-.678c-.213-.166-.48-.249-.799-.249-.308 0-.568.08-.781.238-.21.159-.37.38-.48.664a2.77 2.77 0 0 0-.163.995Zm7.485 2.837c-.538 0-1-.115-1.389-.344a2.336 2.336 0 0 1-.894-.977c-.209-.421-.313-.915-.313-1.48 0-.56.104-1.052.313-1.478.21-.426.504-.759.88-.998.379-.239.822-.359 1.328-.359.308 0 .606.051.895.153.29.102.548.262.778.48.23.217.41.5.543.848.133.346.2.766.2 1.26v.377H9.556v-.795h3.296c0-.28-.057-.527-.17-.742a1.29 1.29 0 0 0-1.198-.703 1.38 1.38 0 0 0-.78.22 1.474 1.474 0 0 0-.512.568 1.641 1.641 0 0 0-.178.756v.622c0 .364.064.674.192.93.13.256.311.451.543.586.232.133.503.199.814.199.2 0 .384-.028.55-.085a1.143 1.143 0 0 0 .707-.692l1.005.18a1.82 1.82 0 0 1-.434.778 2.1 2.1 0 0 1-.777.515 2.91 2.91 0 0 1-1.062.181Zm6.064-5.565v.853h-2.979v-.853h2.98Zm-2.18-1.306h1.062v5.16c0 .205.03.36.092.465a.49.49 0 0 0 .238.21c.1.035.207.052.323.052.085 0 .16-.005.224-.017l.149-.029.192.877a2.08 2.08 0 0 1-.689.114 1.87 1.87 0 0 1-.781-.15 1.34 1.34 0 0 1-.586-.482c-.15-.218-.224-.491-.224-.82v-5.38Zm4.942 6.882c-.345 0-.658-.064-.937-.192a1.58 1.58 0 0 1-.664-.565c-.161-.246-.242-.548-.242-.905 0-.308.06-.561.178-.76a1.31 1.31 0 0 1 .48-.472c.2-.116.425-.204.674-.263a6.56 6.56 0 0 1 .76-.135l.795-.092c.204-.027.352-.068.444-.125.092-.057.139-.149.139-.277V5.31c0-.31-.088-.55-.263-.72-.173-.171-.431-.256-.774-.256-.358 0-.64.08-.845.238-.204.156-.345.33-.423.522l-.998-.228a1.92 1.92 0 0 1 .519-.802c.23-.206.493-.355.791-.448a3.116 3.116 0 0 1 1.634-.063c.246.05.476.142.689.277.215.134.392.327.53.578.136.249.205.572.205.97V9h-1.037v-.746h-.043a1.512 1.512 0 0 1-.308.405 1.642 1.642 0 0 1-.53.33 2.053 2.053 0 0 1-.774.132Zm.231-.853c.294 0 .545-.058.753-.174a1.192 1.192 0 0 0 .646-1.061V6.33a.552.552 0 0 1-.22.106 3.43 3.43 0 0 1-.366.082l-.401.06c-.13.017-.24.03-.327.043a2.63 2.63 0 0 0-.564.131.97.97 0 0 0-.405.266.665.665 0 0 0-.15.455c0 .263.098.462.292.597.194.132.441.198.742.198Z" fill="#fff"/>
</svg>
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="12" height="12" rx="2" fill="#F56565"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.42 2.494c-.373-.313-.896-.49-1.5-.49a2.028 2.028 0 0 0-1.52.567 1.948 1.948 0 0 0-.453.685c-.1.257-.143.531-.127.805V10h.79V8.508a2.46 2.46 0 0 0 1.59.549c.3.01.6-.04.88-.147a2.21 2.21 0 0 0 .751-.48c.215-.208.383-.457.495-.733.112-.274.165-.568.157-.863a1.925 1.925 0 0 0-.309-1.09 1.978 1.978 0 0 0-.717-.664c.157-.147.287-.32.381-.514.12-.245.18-.515.176-.787 0-.523-.22-.97-.594-1.285Zm-2.023.36c.163-.06.338-.085.512-.074h.008c.413 0 .741.105.964.28.22.171.343.417.343.72v.004a.944.944 0 0 1-.288.713.99.99 0 0 1-.732.283l-.115-.004v.781h.112c.467 0 .84.13 1.094.351.253.219.398.534.398.927v.004a1.357 1.357 0 0 1-.418 1.04 1.422 1.422 0 0 1-1.069.4h-.012a1.532 1.532 0 0 1-1.109-.393 1.437 1.437 0 0 1-.475-1.052V4.05a1.165 1.165 0 0 1 .352-.923c.123-.12.272-.214.435-.274Z" fill="#fff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.42 2.494c-.373-.313-.896-.49-1.5-.49a2.028 2.028 0 0 0-1.52.567 1.948 1.948 0 0 0-.453.685 1.91 1.91 0 0 0-.127.805V10h.79V8.508a2.46 2.46 0 0 0 1.59.549c.3.01.6-.04.88-.147a2.21 2.21 0 0 0 .751-.48 2.136 2.136 0 0 0 .652-1.596 1.925 1.925 0 0 0-.309-1.09 1.978 1.978 0 0 0-.717-.664 1.75 1.75 0 0 0 .557-1.301c0-.523-.22-.97-.594-1.285Zm-2.023.36c.163-.06.338-.085.512-.074h.008c.413 0 .741.105.964.28.22.171.343.417.343.72v.004a.944.944 0 0 1-.288.713.99.99 0 0 1-.732.283l-.115-.004v.781h.112c.467 0 .84.13 1.094.351.253.219.398.534.398.927v.004a1.357 1.357 0 0 1-.418 1.04 1.422 1.422 0 0 1-1.069.4h-.012a1.532 1.532 0 0 1-1.109-.393 1.437 1.437 0 0 1-.475-1.052V4.05a1.165 1.165 0 0 1 .352-.923c.123-.12.272-.214.435-.274Z" fill="#fff"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.4 2a1.8 1.8 0 0 0-1.8 1.8v14.8A1.4 1.4 0 0 0 3 20h1.6v-1.326H3V3.516h1.6V2H3.4Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.992.45C4.244.163 4.585 0 4.94 0h8.038a.63.63 0 0 1 .474.225L18.14 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.392 1.087-.252.289-.593.451-.948.451H4.94c-.356 0-.696-.162-.948-.45a1.661 1.661 0 0 1-.392-1.088V1.538c0-.408.141-.799.392-1.087Zm.948 1.088h6.87v4.497c0 .388.315.702.702.702h4.485v11.725H4.94V1.538Zm8.274.59 2.791 3.205h-2.79V2.128Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.992.45C4.244.163 4.585 0 4.94 0h8.038a.63.63 0 0 1 .474.225L18.14 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.392 1.087a1.26 1.26 0 0 1-.948.451H4.94c-.356 0-.696-.162-.948-.45a1.661 1.661 0 0 1-.392-1.088V1.538c0-.408.141-.799.392-1.087Zm.948 1.088h6.87v4.497c0 .388.315.702.702.702h4.485v11.725H4.94V1.538Zm8.274.59 2.791 3.205h-2.79V2.128Z" fill="currentColor"/>
<rect x="7.2" y="14.3" width="7.8" height="1.2" rx=".6" fill="currentColor"/>
<rect x="7.2" y="12" width="7.8" height="1.2" rx=".6" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.4 2a1.8 1.8 0 0 0-1.8 1.8v14.8A1.4 1.4 0 0 0 3 20h1.6v-1.326H3V3.516h1.6V2H3.4Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.992.45C4.244.163 4.585 0 4.94 0h8.038a.63.63 0 0 1 .474.225L18.14 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.392 1.087-.252.289-.593.451-.948.451H4.94c-.356 0-.696-.162-.948-.45a1.661 1.661 0 0 1-.392-1.088V1.538c0-.408.141-.799.392-1.087Zm8.709 1.088H4.94v16.924h12.057V6.472l-4.296-4.934Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.992.45C4.244.163 4.585 0 4.94 0h8.038a.63.63 0 0 1 .474.225L18.14 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.392 1.087a1.26 1.26 0 0 1-.948.451H4.94c-.356 0-.696-.162-.948-.45a1.661 1.661 0 0 1-.392-1.088V1.538c0-.408.141-.799.392-1.087Zm8.709 1.088H4.94v16.924h12.057V6.472l-4.296-4.934Z" fill="currentColor"/>
<path d="m7.9 13.357 2.2 2.357L14.5 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.512.42c.388 0 .702.315.702.703v4.21h4.21a.702.702 0 1 1 0 1.404h-4.912a.702.702 0 0 1-.702-.702V1.123c0-.388.315-.702.702-.702Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.76 18.333a.603.603 0 0 1-.294-.075L10 15.798l-4.467 2.46a.607.607 0 0 1-.663-.052.657.657 0 0 1-.213-.285.69.69 0 0 1-.038-.36l.853-5.21-3.615-3.69a.69.69 0 0 1-.16-.677.663.663 0 0 1 .194-.3c.09-.08.199-.131.315-.149l4.995-.76 2.233-4.74a.65.65 0 0 1 .233-.269.61.61 0 0 1 .666 0c.1.065.18.158.232.269l2.234 4.74 4.994.76c.116.018.226.07.316.149.09.079.157.183.193.3a.69.69 0 0 1-.16.678l-3.615 3.69.854 5.21a.692.692 0 0 1-.14.537.636.636 0 0 1-.216.173.607.607 0 0 1-.266.061h.001Z" fill="currentColor"/>
<path d="M14.76 18.333a.603.603 0 0 1-.294-.075L10 15.798l-4.467 2.46a.607.607 0 0 1-.663-.052.657.657 0 0 1-.213-.285.69.69 0 0 1-.038-.36l.853-5.21-3.615-3.69a.69.69 0 0 1-.16-.677.663.663 0 0 1 .194-.3.615.615 0 0 1 .315-.149l4.995-.76 2.233-4.74a.65.65 0 0 1 .233-.269.61.61 0 0 1 .666 0c.1.065.18.158.232.269l2.234 4.74 4.994.76c.116.018.226.07.316.149.09.079.157.183.193.3a.69.69 0 0 1-.16.678l-3.615 3.69.854 5.21a.692.692 0 0 1-.14.537.636.636 0 0 1-.216.173.607.607 0 0 1-.266.061h.001Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.76 18.333a.603.603 0 0 1-.294-.075l.293.075Zm.003 0c.09 0 .18-.021.262-.061.083-.04.157-.1.216-.173a.674.674 0 0 0 .14-.538l-.854-5.21 3.616-3.69a.69.69 0 0 0 .16-.677.662.662 0 0 0-.194-.3.617.617 0 0 0-.316-.149l-4.884-.743a.208.208 0 0 1-.158-.117l-2.186-4.64a.651.651 0 0 0-.232-.269.61.61 0 0 0-.666 0 .65.65 0 0 0-.233.269l-2.186 4.64a.208.208 0 0 1-.157.117l-4.885.743a.617.617 0 0 0-.315.149.663.663 0 0 0-.194.3.69.69 0 0 0 .16.678L5.4 12.276a.208.208 0 0 1 .056.18L4.62 17.56a.69.69 0 0 0 .038.36.657.657 0 0 0 .213.285.613.613 0 0 0 .663.052l4.366-2.405a.208.208 0 0 1 .201 0l4.366 2.405m-7.795-2.915c-.028.172.154.3.306.216L9.9 13.95a.208.208 0 0 1 .201 0l2.922 1.61a.208.208 0 0 0 .306-.216l-.565-3.452a.208.208 0 0 1 .057-.18l2.485-2.536a.208.208 0 0 0-.117-.351l-3.409-.52a.208.208 0 0 1-.157-.116l-1.434-3.044a.208.208 0 0 0-.377 0L8.377 8.189a.208.208 0 0 1-.157.117l-3.408.518a.208.208 0 0 0-.118.352l2.486 2.537a.208.208 0 0 1 .057.18l-.566 3.45Zm8.092 2.99h-.003.003Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.76 18.333a.603.603 0 0 1-.294-.075l.293.075Zm.003 0a.6.6 0 0 0 .262-.061c.083-.04.157-.1.216-.173a.674.674 0 0 0 .14-.538l-.854-5.21 3.616-3.69a.69.69 0 0 0 .16-.677.662.662 0 0 0-.194-.3.617.617 0 0 0-.316-.149l-4.884-.743a.208.208 0 0 1-.158-.117l-2.186-4.64a.651.651 0 0 0-.232-.269.61.61 0 0 0-.666 0 .65.65 0 0 0-.233.269l-2.186 4.64a.208.208 0 0 1-.157.117l-4.885.743a.617.617 0 0 0-.315.149.663.663 0 0 0-.194.3.69.69 0 0 0 .16.678L5.4 12.276a.208.208 0 0 1 .056.18L4.62 17.56a.69.69 0 0 0 .038.36.657.657 0 0 0 .213.285.613.613 0 0 0 .663.052L9.9 15.852a.208.208 0 0 1 .201 0l4.366 2.405m-7.795-2.915c-.028.172.154.3.306.216L9.9 13.95a.208.208 0 0 1 .201 0l2.922 1.61a.208.208 0 0 0 .306-.216l-.565-3.452a.208.208 0 0 1 .057-.18l2.485-2.536a.208.208 0 0 0-.117-.351l-3.409-.52a.208.208 0 0 1-.157-.116l-1.434-3.044a.208.208 0 0 0-.377 0L8.377 8.189a.208.208 0 0 1-.157.117l-3.408.518a.208.208 0 0 0-.118.352l2.486 2.537a.208.208 0 0 1 .057.18l-.566 3.45Zm8.092 2.99h-.003.003Z" fill="currentColor"/>
</svg>
......@@ -15,13 +15,9 @@ import 'lib/setLocale';
const PAGE_PROPS = {
cookies: '',
referrer: '',
id: '',
height_or_hash: '',
hash: '',
number: '',
q: '',
name: '',
adBannerProvider: '',
query: {},
adBannerProvider: undefined,
apiData: null,
};
const TestApp = ({ children }: {children: React.ReactNode}) => {
......
......@@ -34,7 +34,7 @@ import type {
import type { AddressesResponse } from 'types/api/addresses';
import type { TxBlobs, Blob } from 'types/api/blobs';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts';
import type { BackendVersionConfig } from 'types/api/configs';
import type {
SmartContract,
......@@ -88,6 +88,7 @@ import type {
TransactionsResponseWatchlist,
TransactionsSorting,
TransactionsResponseWithBlobs,
TransactionsStats,
} from 'types/api/transaction';
import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import type { TTxsFilters, TTxsWithBlobsFilters } from 'types/api/txsFilters';
......@@ -276,6 +277,9 @@ export const RESOURCES = {
pathParams: [ 'height_or_hash' as const ],
filterFields: [],
},
txs_stats: {
path: '/api/v2/transactions/stats',
},
txs_validated: {
path: '/api/v2/transactions',
filterFields: [ 'filter' as const, 'type' as const, 'method' as const ],
......@@ -540,6 +544,9 @@ export const RESOURCES = {
stats_charts_market: {
path: '/api/v2/stats/charts/market',
},
stats_charts_secondary_coin_price: {
path: '/api/v2/stats/charts/secondary-coin-market',
},
// HOMEPAGE
homepage_blocks: {
......@@ -823,6 +830,7 @@ Q extends 'token_info_applications' ? TokenInfoApplications :
Q extends 'stats' ? HomeStats :
Q extends 'stats_charts_txs' ? ChartTransactionResponse :
Q extends 'stats_charts_market' ? ChartMarketResponse :
Q extends 'stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse :
Q extends 'homepage_blocks' ? Array<Block> :
Q extends 'homepage_txs' ? Array<Transaction> :
Q extends 'homepage_txs_watchlist' ? Array<Transaction> :
......@@ -838,6 +846,7 @@ Q extends 'blocks' ? BlocksResponse :
Q extends 'block' ? Block :
Q extends 'block_txs' ? BlockTransactionsResponse :
Q extends 'block_withdrawals' ? BlockWithdrawalsResponse :
Q extends 'txs_stats' ? TransactionsStats :
Q extends 'txs_validated' ? TransactionsResponseValidated :
Q extends 'txs_pending' ? TransactionsResponsePending :
Q extends 'txs_with_blobs' ? TransactionsResponseWithBlobs :
......
......@@ -10,13 +10,9 @@ type Props = {
const AppContext = createContext<PageProps>({
cookies: '',
referrer: '',
id: '',
height_or_hash: '',
hash: '',
number: '',
q: '',
name: '',
adBannerProvider: '',
query: {},
adBannerProvider: undefined,
apiData: null,
});
export function AppContextProvider({ children, pageProps }: Props) {
......
import React from 'react';
export default function useIsMounted() {
const [ isMounted, setIsMounted ] = React.useState(false);
React.useEffect(() => {
setIsMounted(true);
}, [ ]);
return isMounted;
}
......@@ -4,26 +4,29 @@ import type { Route } from 'nextjs-routes';
import generate from './generate';
interface TestCase<R extends Route> {
interface TestCase<Pathname extends Route['pathname']> {
title: string;
route: R;
apiData?: ApiData<R>;
route: {
pathname: Pathname;
query?: Route['query'];
};
apiData?: ApiData<Pathname>;
}
const TEST_CASES: Array<TestCase<Route>> = [
const TEST_CASES = [
{
title: 'static route',
route: {
pathname: '/blocks',
},
},
} as TestCase<'/blocks'>,
{
title: 'dynamic route',
route: {
pathname: '/tx/[hash]',
query: { hash: '0x12345' },
},
},
} as TestCase<'/tx/[hash]'>,
{
title: 'dynamic route with API data',
route: {
......@@ -31,7 +34,7 @@ const TEST_CASES: Array<TestCase<Route>> = [
query: { hash: '0x12345' },
},
apiData: { symbol: 'USDT' },
} as TestCase<{ pathname: '/token/[hash]'; query: { hash: string }}>,
} as TestCase<'/token/[hash]'>,
];
describe('generates correct metadata for:', () => {
......
import type { ApiData, Metadata } from './types';
import type { RouteParams } from 'nextjs/types';
import type { Route } from 'nextjs-routes';
......@@ -9,7 +10,7 @@ import compileValue from './compileValue';
import getPageOgType from './getPageOgType';
import * as templates from './templates';
export default function generate<R extends Route>(route: R, apiData?: ApiData<R>): Metadata {
export default function generate<Pathname extends Route['pathname']>(route: RouteParams<Pathname>, apiData: ApiData<Pathname> = null): Metadata {
const params = {
...route.query,
...apiData,
......@@ -17,7 +18,7 @@ export default function generate<R extends Route>(route: R, apiData?: ApiData<R>
network_title: getNetworkTitle(),
};
const compiledTitle = compileValue(templates.title.make(route.pathname), params);
const compiledTitle = compileValue(templates.title.make(route.pathname, Boolean(apiData)), params);
const title = compiledTitle ? compiledTitle + (config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : '') : '';
const description = compileValue(templates.description.make(route.pathname), params);
......
import type { IncomingMessage, ServerResponse } from 'http';
import { httpLogger } from 'nextjs/utils/logger';
import metrics from 'lib/monitoring/metrics';
export default async function getApiDataForSocialPreview(req: IncomingMessage | undefined, res: ServerResponse<IncomingMessage> | undefined, pathname: string) {
if (!req || !res || !metrics) {
return;
}
const userAgent = req.headers['user-agent'];
if (!userAgent) {
return;
}
if (userAgent.toLowerCase().includes('twitter')) {
httpLogger(req, res);
metrics.requestCounter.inc({ route: pathname, bot: 'twitter' });
}
if (userAgent.toLowerCase().includes('facebook')) {
httpLogger(req, res);
metrics.requestCounter.inc({ route: pathname, bot: 'facebook' });
}
if (userAgent.toLowerCase().includes('telegram')) {
httpLogger(req, res);
metrics.requestCounter.inc({ route: pathname, bot: 'telegram' });
}
}
export { default as generate } from './generate';
export { default as update } from './update';
export * from './types';
......@@ -13,10 +13,10 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/contract-verification': 'verify contract',
'/address/[hash]/contract-verification': 'contract verification for %hash%',
'/tokens': 'tokens',
'/token/[hash]': '%symbol% token details',
'/token/[hash]': 'token details',
'/token/[hash]/instance/[id]': 'NFT instance',
'/apps': 'apps marketplace',
'/apps/[id]': '- %app_name%',
'/apps/[id]': 'marketplace app',
'/stats': 'statistics',
'/api-docs': 'REST API',
'/graphiql': 'GraphQL',
......@@ -56,8 +56,15 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/auth/unverified-email': 'unverified email',
};
export function make(pathname: Route['pathname']) {
const template = TEMPLATE_MAP[pathname];
const TEMPLATE_MAP_ENHANCED: Partial<Record<Route['pathname'], string>> = {
'/token/[hash]': '%symbol% token details',
'/token/[hash]/instance/[id]': 'token instance for %symbol%',
'/apps/[id]': '- %app_name%',
'/address/[hash]': 'address details for %domain_name%',
};
export function make(pathname: Route['pathname'], isEnriched = false) {
const template = (isEnriched ? TEMPLATE_MAP_ENHANCED[pathname] : undefined) ?? TEMPLATE_MAP[pathname];
return `%network_name% ${ template }`;
}
import type { Route } from 'nextjs-routes';
/* eslint-disable @typescript-eslint/indent */
export type ApiData<R extends Route> =
R['pathname'] extends '/token/[hash]' ? { symbol: string } :
R['pathname'] extends '/token/[hash]/instance/[id]' ? { symbol: string } :
R['pathname'] extends '/apps/[id]' ? { app_name: string } :
never;
export type ApiData<Pathname extends Route['pathname']> =
(
Pathname extends '/address/[hash]' ? { domain_name: string } :
Pathname extends '/token/[hash]' ? { symbol: string } :
Pathname extends '/token/[hash]/instance/[id]' ? { symbol: string } :
Pathname extends '/apps/[id]' ? { app_name: string } :
never
) | null;
export interface Metadata {
title: string;
......
import type { ApiData } from './types';
import type { RouteParams } from 'nextjs/types';
import type { Route } from 'nextjs-routes';
import generate from './generate';
export default function update<R extends Route>(route: R, apiData: ApiData<R>) {
export default function update<Pathname extends Route['pathname']>(route: RouteParams<Pathname>, apiData: ApiData<Pathname>) {
const { title, description } = generate(route, apiData);
window.document.title = title;
......
......@@ -8,13 +8,26 @@ const metrics = (() => {
promClient.register.clear();
const requestCounter = new promClient.Counter({
name: 'request_counter',
help: 'Number of incoming requests',
const socialPreviewBotRequests = new promClient.Counter({
name: 'social_preview_bot_requests_total',
help: 'Number of incoming requests from social preview bots',
labelNames: [ 'route', 'bot' ] as const,
});
return { requestCounter };
const searchEngineBotRequests = new promClient.Counter({
name: 'search_engine_bot_requests_total',
help: 'Number of incoming requests from search engine bots',
labelNames: [ 'route', 'bot' ] as const,
});
const apiRequestDuration = new promClient.Histogram({
name: 'api_request_duration_seconds',
help: 'Duration of requests to API in seconds',
labelNames: [ 'route', 'code' ],
buckets: [ 0.2, 0.5, 1, 3, 10 ],
});
return { socialPreviewBotRequests, searchEngineBotRequests, apiRequestDuration };
})();
export default metrics;
......@@ -61,6 +61,11 @@ export const withoutBothPrices = {
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null),
};
export const withSecondaryCoin = {
...base,
secondary_coin_price: '3.398',
};
export const noChartData = {
...base,
transactions_today: null,
......
import type { TransactionsStats } from 'types/api/transaction';
export const base: TransactionsStats = {
pending_transactions_count: '4200',
transaction_fees_avg_24h: '22342870314428',
transaction_fees_sum_24h: '22184012506492688277',
transactions_count_24h: '992890',
};
......@@ -2,6 +2,7 @@ import Head from 'next/head';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
import config from 'configs/app';
import useAdblockDetect from 'lib/hooks/useAdblockDetect';
......@@ -10,14 +11,17 @@ import * as metadata from 'lib/metadata';
import * as mixpanel from 'lib/mixpanel';
import { init as initSentry } from 'lib/sentry/config';
type Props = Route & {
interface Props<Pathname extends Route['pathname']> {
pathname: Pathname;
children: React.ReactNode;
query?: PageProps<Pathname>['query'];
apiData?: PageProps<Pathname>['apiData'];
}
initSentry();
const PageNextJs = (props: Props) => {
const { title, description, opengraph } = metadata.generate(props);
const PageNextJs = <Pathname extends Route['pathname']>(props: Props<Pathname>) => {
const { title, description, opengraph } = metadata.generate(props, props.apiData);
useGetCsrfToken();
useAdblockDetect();
......
import type { GetServerSideProps } from 'next';
import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
import type { AdBannerProviders } from 'types/client/adProviders';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import isNeedProxy from 'lib/api/isNeedProxy';
const rollupFeature = config.features.rollup;
const adBannerFeature = config.features.adsBanner;
import type * as metadata from 'lib/metadata';
export type Props = {
export interface Props<Pathname extends Route['pathname'] = never> {
query: Route['query'];
cookies: string;
referrer: string;
id: string;
height_or_hash: string;
hash: string;
number: string;
q: string;
name: string;
adBannerProvider: string;
adBannerProvider: AdBannerProviders | undefined;
// 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<Pathname> | null;
}
export const base: GetServerSideProps<Props> = async({ req, query }) => {
export const base = async <Pathname extends Route['pathname'] = never>({ req, query }: GetServerSidePropsContext):
Promise<GetServerSidePropsResult<Props<Pathname>>> => {
const adBannerProvider = (() => {
if (adBannerFeature.isEnabled) {
if ('additionalProvider' in adBannerFeature && adBannerFeature.additionalProvider) {
......@@ -28,20 +32,16 @@ export const base: GetServerSideProps<Props> = async({ req, query }) => {
return adBannerFeature.provider;
}
}
return '';
return;
})();
return {
props: {
query,
cookies: req.headers.cookie || '',
referrer: req.headers.referer || '',
id: query.id?.toString() || '',
hash: query.hash?.toString() || '',
height_or_hash: query.height_or_hash?.toString() || '',
number: query.number?.toString() || '',
q: query.q?.toString() || '',
name: query.name?.toString() || '',
adBannerProvider,
apiData: null,
},
};
};
......@@ -119,14 +119,15 @@ export const batch: GetServerSideProps<Props> = async(context) => {
return base(context);
};
export const marketplace: GetServerSideProps<Props> = async(context) => {
export const marketplace = async <Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext):
Promise<GetServerSidePropsResult<Props<Pathname>>> => {
if (!config.features.marketplace.isEnabled) {
return {
notFound: true,
};
}
return base(context);
return base<Pathname>(context);
};
export const apiDocs: GetServerSideProps<Props> = async(context) => {
......
import type { NextPage } from 'next';
import type { Route } from 'nextjs-routes';
// eslint-disable-next-line @typescript-eslint/ban-types
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
}
export interface RouteParams<Pathname extends Route['pathname']> {
pathname: Pathname;
query?: Route['query'];
}
import type { IncomingMessage } from 'http';
type SocialPreviewBot = 'twitter' | 'facebook' | 'telegram' | 'slack';
type SearchEngineBot = 'google' | 'bing' | 'yahoo' | 'duckduckgo';
type ReturnType = {
type: 'social_preview';
bot: SocialPreviewBot;
} | {
type: 'search_engine';
bot: SearchEngineBot;
} | undefined
export default function detectBotRequest(req: IncomingMessage): ReturnType {
const userAgent = req.headers['user-agent'];
if (!userAgent) {
return;
}
if (userAgent.toLowerCase().includes('twitter')) {
return { type: 'social_preview', bot: 'twitter' };
}
if (userAgent.toLowerCase().includes('facebook')) {
return { type: 'social_preview', bot: 'facebook' };
}
if (userAgent.toLowerCase().includes('telegram')) {
return { type: 'social_preview', bot: 'telegram' };
}
if (userAgent.toLowerCase().includes('slack')) {
return { type: 'social_preview', bot: 'slack' };
}
if (userAgent.toLowerCase().includes('googlebot')) {
return { type: 'search_engine', bot: 'google' };
}
if (userAgent.toLowerCase().includes('bingbot')) {
return { type: 'search_engine', bot: 'bing' };
}
if (userAgent.toLowerCase().includes('yahoo')) {
return { type: 'search_engine', bot: 'yahoo' };
}
if (userAgent.toLowerCase().includes('duckduck')) {
return { type: 'search_engine', bot: 'duckduckgo' };
}
}
import fetch, { AbortError } from 'node-fetch';
import buildUrl from 'nextjs/utils/buildUrl';
import { httpLogger } from 'nextjs/utils/logger';
import { RESOURCES } from 'lib/api/resources';
import type { ResourceName, ResourcePathParams, ResourcePayload } from 'lib/api/resources';
import { SECOND } from 'lib/consts';
import metrics from 'lib/monitoring/metrics';
type Params<R extends ResourceName> = (
{
resource: R;
pathParams?: ResourcePathParams<R>;
} | {
url: string;
route: string;
}
) & {
timeout?: number;
}
export default async function fetchApi<R extends ResourceName = never, S = ResourcePayload<R>>(params: Params<R>): Promise<S | undefined> {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, params.timeout || SECOND);
const url = 'url' in params ? params.url : buildUrl(params.resource, params.pathParams);
const route = 'route' in params ? params.route : RESOURCES[params.resource]['path'];
const end = metrics?.apiRequestDuration.startTimer();
try {
const response = await fetch(url, { signal: controller.signal });
const duration = end?.({ route, code: response.status });
if (response.status === 200) {
httpLogger.logger.info({ message: 'API fetch', url, code: response.status, duration });
} else {
httpLogger.logger.error({ message: 'API fetch', url, code: response.status, duration });
}
return await response.json() as Promise<S>;
} catch (error) {
const code = error instanceof AbortError ? 504 : 500;
const duration = end?.({ route, code });
httpLogger.logger.error({ message: 'API fetch', url, code, duration });
} finally {
clearTimeout(timeout);
}
}
......@@ -30,14 +30,9 @@ export default function fetchFactory(
};
httpLogger.logger.info({
message: 'Trying to call API',
message: 'API fetch via Next.js proxy',
url,
req: _req,
});
httpLogger.logger.info({
message: 'API request headers',
headers,
// headers,
});
const body = (() => {
......
import type { IncomingMessage, ServerResponse } from 'http';
import metrics from 'lib/monitoring/metrics';
import detectBotRequest from './detectBotRequest';
export default async function logRequestFromBot(req: IncomingMessage | undefined, res: ServerResponse<IncomingMessage> | undefined, pathname: string) {
if (!req || !res || !metrics) {
return;
}
const botInfo = detectBotRequest(req);
if (!botInfo) {
return;
}
switch (botInfo.type) {
case 'search_engine': {
metrics.searchEngineBotRequests.inc({ route: pathname, bot: botInfo.bot });
return;
}
case 'social_preview': {
metrics.socialPreviewBotRequests.inc({ route: pathname, bot: botInfo.bot });
return;
}
}
}
......@@ -3,9 +3,9 @@ import type { DocumentContext } from 'next/document';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import React from 'react';
import logRequestFromBot from 'nextjs/utils/logRequestFromBot';
import * as serverTiming from 'nextjs/utils/serverTiming';
import getApiDataForSocialPreview from 'lib/metadata/getApiDataForSocialPreview';
import theme from 'theme';
import * as svgSprite from 'ui/shared/IconSvg';
......@@ -22,7 +22,7 @@ class MyDocument extends Document {
return result;
};
await getApiDataForSocialPreview(ctx.req, ctx.res, ctx.pathname);
await logRequestFromBot(ctx.req, ctx.res, ctx.pathname);
const initialProps = await Document.getInitialProps(ctx);
......
......@@ -8,7 +8,7 @@ import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddr
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/address/[hash]/contract-verification" query={ props }>
<PageNextJs pathname="/address/[hash]/contract-verification" query={ props.query }>
<ContractVerificationForAddress/>
</PageNextJs>
);
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import type { GetServerSideProps, NextPage } from 'next';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
import * as gSSP from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
const Address = dynamic(() => import('ui/pages/Address'), { ssr: false });
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
import Address from 'ui/pages/Address';
const Page: NextPage<Props> = (props: Props) => {
const pathname: Route['pathname'] = '/address/[hash]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname="/address/[hash]" query={ props }>
<PageNextJs pathname="/address/[hash]" query={ props.query } apiData={ props.apiData }>
<Address/>
</PageNextJs>
);
......@@ -17,4 +24,24 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => {
const baseResponse = await gSSP.base<typeof pathname>(ctx);
if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
const botInfo = detectBotRequest(ctx.req);
if (botInfo?.type === 'social_preview') {
const addressData = await fetchApi({
resource: 'address',
pathParams: { hash: getQueryParamString(ctx.query.hash) },
timeout: 1_000,
});
(await baseResponse.props).apiData = addressData && addressData.ens_domain_name ? {
domain_name: addressData.ens_domain_name,
} : null;
}
}
return baseResponse;
};
import type { NextApiRequest, NextApiResponse } from 'next';
import buildUrl from 'nextjs/utils/buildUrl';
import fetchFactory from 'nextjs/utils/fetch';
import fetchFactory from 'nextjs/utils/fetchProxy';
import { httpLogger } from 'nextjs/utils/logger';
export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) {
......
......@@ -3,16 +3,20 @@ import nodeFetch from 'node-fetch';
import { httpLogger } from 'nextjs/utils/logger';
import metrics from 'lib/monitoring/metrics';
import getQueryParamString from 'lib/router/getQueryParamString';
export default async function mediaTypeHandler(req: NextApiRequest, res: NextApiResponse) {
httpLogger(req, res);
try {
const url = getQueryParamString(req.query.url);
const end = metrics?.apiRequestDuration.startTimer();
const response = await nodeFetch(url, { method: 'HEAD' });
const duration = end?.({ route: '/media-type', code: response.status });
if (response.status !== 200) {
httpLogger.logger.error({ message: 'API fetch', url, code: response.status, duration });
throw new Error();
}
......@@ -30,6 +34,8 @@ export default async function mediaTypeHandler(req: NextApiRequest, res: NextApi
return 'html';
}
})();
httpLogger.logger.info({ message: 'API fetch', url, code: response.status, duration });
res.status(200).json({ type: mediaType });
} catch (error) {
res.status(200).json({ type: undefined });
......
......@@ -4,7 +4,7 @@ import * as promClient from 'prom-client';
// eslint-disable-next-line no-restricted-properties
const isEnabled = process.env.PROMETHEUS_METRICS_ENABLED === 'true';
isEnabled && promClient.collectDefaultMetrics();
isEnabled && promClient.collectDefaultMetrics({ prefix: 'frontend_' });
export default async function metricsHandler(req: NextApiRequest, res: NextApiResponse) {
const metrics = await promClient.register.metrics();
......
......@@ -2,7 +2,7 @@ import _pick from 'lodash/pick';
import _pickBy from 'lodash/pickBy';
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'nextjs/utils/fetch';
import fetchFactory from 'nextjs/utils/fetchProxy';
import appConfig from 'configs/app';
......
import type { GetServerSideProps } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { NextPageWithLayout } from 'nextjs/types';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
import type { Route } from 'nextjs-routes';
import * as gSSP from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
import LayoutApp from 'ui/shared/layout/LayoutApp';
const MarketplaceApp = dynamic(() => import('ui/pages/MarketplaceApp'), { ssr: false });
const Page: NextPageWithLayout<Props> = (props: Props) => {
const pathname: Route['pathname'] = '/apps/[id]';
const feature = config.features.marketplace;
const Page: NextPageWithLayout<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname="/apps/[id]" query={ props }>
<PageNextJs pathname="/apps/[id]" query={ props.query } apiData={ props.apiData }>
<MarketplaceApp/>
</PageNextJs>
);
......@@ -28,4 +39,40 @@ Page.getLayout = function getLayout(page: React.ReactElement) {
export default Page;
export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps';
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => {
const baseResponse = await gSSP.marketplace<typeof pathname>(ctx);
if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse && feature.isEnabled) {
const botInfo = detectBotRequest(ctx.req);
if (botInfo?.type === 'social_preview') {
const appData = await(async() => {
if ('configUrl' in feature) {
const appList = await fetchApi<never, Array<MarketplaceAppOverview>>({
url: config.app.baseUrl + feature.configUrl,
route: '/marketplace_config',
timeout: 1_000,
});
if (appList && Array.isArray(appList)) {
return appList.find(app => app.id === getQueryParamString(ctx.query.id));
}
} else {
return await fetchApi({
resource: 'marketplace_dapp',
pathParams: { dappId: getQueryParamString(ctx.query.id), chainId: config.chain.id },
timeout: 1_000,
});
}
})();
(await baseResponse.props).apiData = appData && appData.title ? {
app_name: appData.title,
} : null;
}
}
return baseResponse;
};
......@@ -25,7 +25,7 @@ const Batch = dynamic(() => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/batches/[number]" query={ props }>
<PageNextJs pathname="/batches/[number]" query={ props.query }>
<Batch/>
</PageNextJs>
);
......
......@@ -9,7 +9,7 @@ const Blob = dynamic(() => import('ui/pages/Blob'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/blobs/[hash]" query={ props }>
<PageNextJs pathname="/blobs/[hash]" query={ props.query }>
<Blob/>
</PageNextJs>
);
......
......@@ -9,7 +9,7 @@ const Block = dynamic(() => import('ui/pages/Block'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/block/[height_or_hash]" query={ props }>
<PageNextJs pathname="/block/[height_or_hash]" query={ props.query }>
<Block/>
</PageNextJs>
);
......
......@@ -8,7 +8,7 @@ import ContractVerification from 'ui/pages/ContractVerification';
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/contract-verification" query={ props }>
<PageNextJs pathname="/contract-verification" query={ props.query }>
<ContractVerification/>
</PageNextJs>
);
......
......@@ -9,7 +9,7 @@ const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/name-domains/[name]" query={ props }>
<PageNextJs pathname="/name-domains/[name]" query={ props.query }>
<NameDomain/>
</PageNextJs>
);
......
......@@ -9,7 +9,7 @@ const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/op/[hash]" query={ props }>
<PageNextJs pathname="/op/[hash]" query={ props.query }>
<UserOp/>
</PageNextJs>
);
......
......@@ -12,7 +12,7 @@ const SearchResults = dynamic(() => import('ui/pages/SearchResults'), { ssr: fal
const Page: NextPageWithLayout<Props> = (props: Props) => {
return (
<PageNextJs pathname="/search-results" query={ props }>
<PageNextJs pathname="/search-results" query={ props.query }>
<SearchResults/>
</PageNextJs>
);
......
import type { NextPage } from 'next';
import type { GetServerSideProps, NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
import * as gSSP from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
const Token = dynamic(() => import('ui/pages/Token'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
const pathname: Route['pathname'] = '/token/[hash]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname="/token/[hash]" query={ props }>
<PageNextJs pathname="/token/[hash]" query={ props.query } apiData={ props.apiData }>
<Token/>
</PageNextJs>
);
......@@ -17,4 +26,24 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => {
const baseResponse = await gSSP.base<typeof pathname>(ctx);
if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
const botInfo = detectBotRequest(ctx.req);
if (botInfo?.type === 'social_preview') {
const tokenData = await fetchApi({
resource: 'token',
pathParams: { hash: getQueryParamString(ctx.query.hash) },
timeout: 1_000,
});
(await baseResponse.props).apiData = tokenData && tokenData.symbol ? {
symbol: tokenData.symbol,
} : null;
}
}
return baseResponse;
};
import type { NextPage } from 'next';
import type { GetServerSideProps, NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
import * as gSSP from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import detectBotRequest from 'nextjs/utils/detectBotRequest';
import fetchApi from 'nextjs/utils/fetchApi';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
const TokenInstance = dynamic(() => import('ui/pages/TokenInstance'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
const pathname: Route['pathname'] = '/token/[hash]/instance/[id]';
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => {
return (
<PageNextJs pathname="/token/[hash]/instance/[id]" query={ props }>
<PageNextJs pathname={ pathname } query={ props.query } apiData={ props.apiData }>
<TokenInstance/>
</PageNextJs>
);
......@@ -17,4 +26,24 @@ const Page: NextPage<Props> = (props: Props) => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => {
const baseResponse = await gSSP.base<typeof pathname>(ctx);
if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
const botInfo = detectBotRequest(ctx.req);
if (botInfo?.type === 'social_preview') {
const tokenData = await fetchApi({
resource: 'token',
pathParams: { hash: getQueryParamString(ctx.query.hash) },
timeout: 1_000,
});
(await baseResponse.props).apiData = tokenData && tokenData.symbol ? {
symbol: tokenData.symbol,
} : null;
}
}
return baseResponse;
};
......@@ -9,7 +9,7 @@ const Transaction = dynamic(() => import('ui/pages/Transaction'), { ssr: false }
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/tx/[hash]" query={ props }>
<PageNextJs pathname="/tx/[hash]" query={ props.query }>
<Transaction/>
</PageNextJs>
);
......
......@@ -9,7 +9,7 @@ const KettleTxs = dynamic(() => import('ui/pages/KettleTxs'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/txs/kettle/[hash]" query={ props }>
<PageNextJs pathname="/txs/kettle/[hash]" query={ props.query }>
<KettleTxs/>
</PageNextJs>
);
......
......@@ -24,13 +24,9 @@ const defaultAppContext = {
pageProps: {
cookies: '',
referrer: '',
id: '',
height_or_hash: '',
hash: '',
number: '',
q: '',
name: '',
adBannerProvider: 'slise',
query: {},
adBannerProvider: 'slise' as const,
apiData: null,
},
};
......
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { Transaction } from 'types/api/transaction';
import type { Transaction, TransactionsStats } from 'types/api/transaction';
import { ADDRESS_PARAMS } from './addressParams';
......@@ -59,3 +59,10 @@ export const TX_ZKEVM_L2: Transaction = {
};
export const TX_RAW_TRACE: RawTracesResponse = [];
export const TXS_STATS: TransactionsStats = {
pending_transactions_count: '4200',
transaction_fees_avg_24h: '22342870314428',
transaction_fees_sum_24h: '22184012506492688277',
transactions_count_24h: '992890',
};
......@@ -23,7 +23,7 @@ const sizes = {
minH: 6,
minW: 6,
fontSize: 'sm',
px: 2,
px: 1,
py: '2px',
lineHeight: 5,
},
......
......@@ -18,3 +18,8 @@ export interface ChartMarketResponse {
available_supply: string;
chart_data: Array<ChartMarketItem>;
}
export interface ChartSecondaryCoinPriceResponse {
available_supply: string;
chart_data: Array<ChartMarketItem>;
}
......@@ -16,6 +16,8 @@ export type HomeStats = {
network_utilization_percentage: number;
tvl: string | null;
rootstock_locked_btc?: string | null;
last_output_root_size?: string | null;
secondary_coin_price?: string | null;
}
export type GasPrices = {
......
......@@ -97,6 +97,13 @@ export type Transaction = {
export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ];
export interface TransactionsStats {
pending_transactions_count: string;
transaction_fees_avg_24h: string;
transaction_fees_sum_24h: string;
transactions_count_24h: string;
}
export type TransactionsResponse = TransactionsResponseValidated | TransactionsResponsePending;
export interface TransactionsResponseValidated {
......
export type ChainIndicatorId = 'daily_txs' | 'coin_price' | 'market_cap' | 'tvl';
export const CHAIN_INDICATOR_IDS = [ 'daily_txs', 'coin_price', 'secondary_coin_price', 'market_cap', 'tvl' ] as const;
export type ChainIndicatorId = typeof CHAIN_INDICATOR_IDS[number];
......@@ -7,6 +7,7 @@ import type { NovesHistoryFilterValue } from 'types/api/noves';
import { NovesHistoryFilterValues } from 'types/api/noves';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate';
import { generateListStub } from 'stubs/utils';
......@@ -25,10 +26,12 @@ const getFilterValue = (getFilterValueFromQuery<NovesHistoryFilterValue>).bind(n
type Props = {
scrollRef?: React.RefObject<HTMLDivElement>;
shouldRender?: boolean;
}
const AddressAccountHistory = ({ scrollRef }: Props) => {
const AddressAccountHistory = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
const isMounted = useIsMounted();
const currentAddress = getQueryParamString(router.query.hash).toLowerCase();
......@@ -49,6 +52,10 @@ const AddressAccountHistory = ({ scrollRef }: Props) => {
setFilterValue(newVal);
}, [ ]);
if (!isMounted || !shouldRender) {
return null;
}
const actionBar = (
<ActionBar mt={ -6 } pb={{ base: 6, md: 5 }}>
<AccountHistoryFilter
......
......@@ -8,6 +8,7 @@ import type { AddressBlocksValidatedResponse } from 'types/api/address';
import config from 'configs/app';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMounted from 'lib/hooks/useIsMounted';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { currencyUnits } from 'lib/units';
......@@ -25,12 +26,14 @@ import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksVali
interface Props {
scrollRef?: React.RefObject<HTMLDivElement>;
shouldRender?: boolean;
}
const AddressBlocksValidated = ({ scrollRef }: Props) => {
const AddressBlocksValidated = ({ scrollRef, shouldRender = true }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(false);
const queryClient = useQueryClient();
const router = useRouter();
const isMounted = useIsMounted();
const addressHash = String(router.query.hash);
const query = useQueryWithPages({
......@@ -84,6 +87,10 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
handler: handleNewSocketMessage,
});
if (!isMounted || !shouldRender) {
return null;
}
const content = query.data?.items ? (
<>
{ socketAlert && <SocketAlert mb={ 6 }/> }
......
......@@ -6,6 +6,7 @@ import type { SocketMessage } from 'lib/socket/types';
import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -17,10 +18,16 @@ import SocketAlert from 'ui/shared/SocketAlert';
import AddressCoinBalanceChart from './coinBalance/AddressCoinBalanceChart';
import AddressCoinBalanceHistory from './coinBalance/AddressCoinBalanceHistory';
const AddressCoinBalance = () => {
type Props = {
shouldRender?: boolean;
}
const AddressCoinBalance = ({ shouldRender = true }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(false);
const queryClient = useQueryClient();
const router = useRouter();
const isMounted = useIsMounted();
const scrollRef = React.useRef<HTMLDivElement>(null);
const addressHash = getQueryParamString(router.query.hash);
......@@ -78,6 +85,10 @@ const AddressCoinBalance = () => {
handler: handleNewSocketMessage,
});
if (!isMounted || !shouldRender) {
return null;
}
return (
<>
{ socketAlert && <SocketAlert mb={ 6 }/> }
......
......@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
......@@ -60,6 +61,8 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
has_validated_blocks: false,
}), [ addressHash ]);
const isMounted = useIsMounted();
// error handling (except 404 codes)
if (addressQuery.isError) {
if (isCustomAppError(addressQuery.error)) {
......@@ -74,7 +77,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
const data = addressQuery.isError ? error404Data : addressQuery.data;
if (!data) {
if (!data || !isMounted) {
return null;
}
......
......@@ -6,6 +6,7 @@ import type { AddressFromToFilter } from 'types/api/address';
import { AddressFromToFilterValues } from 'types/api/address';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useIsMounted from 'lib/hooks/useIsMounted';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import { INTERNAL_TX } from 'stubs/internalTx';
......@@ -22,8 +23,14 @@ import AddressIntTxsList from './internals/AddressIntTxsList';
const getFilterValue = (getFilterValueFromQuery<AddressFromToFilter>).bind(null, AddressFromToFilterValues);
const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
type Props = {
scrollRef?: React.RefObject<HTMLDivElement>;
shouldRender?: boolean;
}
const AddressInternalTxs = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
const isMounted = useIsMounted();
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(getFilterValue(router.query.filter));
const hash = getQueryParamString(router.query.hash);
......@@ -55,6 +62,10 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
onFilterChange({ filter: newVal });
}, [ onFilterChange ]);
if (!isMounted || !shouldRender) {
return null;
}
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
......
import { useRouter } from 'next/router';
import React from 'react';
import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { LOG } from 'stubs/log';
import { generateListStub } from 'stubs/utils';
......@@ -12,8 +13,14 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressCsvExportLink from './AddressCsvExportLink';
const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
type Props ={
scrollRef?: React.RefObject<HTMLDivElement>;
shouldRender?: boolean;
}
const AddressLogs = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
const isMounted = useIsMounted();
const hash = getQueryParamString(router.query.hash);
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
......@@ -41,6 +48,10 @@ const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>
</ActionBar>
);
if (!isMounted || !shouldRender) {
return null;
}
const content = data?.items ? data.items.map((item, index) => <LogItem key={ index } { ...item } type="address" isLoading={ isPlaceholderData }/>) : null;
return (
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment