Commit c07fb4e2 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into tom2drum/content-max-width

parents 96191dfb 3e24ed76
...@@ -20,6 +20,7 @@ on: ...@@ -20,6 +20,7 @@ on:
- eth_sepolia - eth_sepolia
- eth_goerli - eth_goerli
- optimism - optimism
- optimism_celestia
- optimism_sepolia - optimism_sepolia
- polygon - polygon
- rootstock - rootstock
......
...@@ -20,6 +20,7 @@ on: ...@@ -20,6 +20,7 @@ on:
- eth_sepolia - eth_sepolia
- eth_goerli - eth_goerli
- optimism - optimism
- optimism_celestia
- optimism_sepolia - optimism_sepolia
- polygon - polygon
- rootstock - rootstock
......
...@@ -368,6 +368,7 @@ ...@@ -368,6 +368,7 @@
"eth_goerli", "eth_goerli",
"eth_sepolia", "eth_sepolia",
"optimism", "optimism",
"optimism_celestia",
"optimism_sepolia", "optimism_sepolia",
"polygon", "polygon",
"rootstock_testnet", "rootstock_testnet",
......
...@@ -25,6 +25,7 @@ export { default as publicTagsSubmission } from './publicTagsSubmission'; ...@@ -25,6 +25,7 @@ export { default as publicTagsSubmission } from './publicTagsSubmission';
export { default as restApiDocs } from './restApiDocs'; export { default as restApiDocs } from './restApiDocs';
export { default as rollup } from './rollup'; export { default as rollup } from './rollup';
export { default as safe } from './safe'; export { default as safe } from './safe';
export { default as saveOnGas } from './saveOnGas';
export { default as sentry } from './sentry'; export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml'; export { default as sol2uml } from './sol2uml';
export { default as stats } from './stats'; export { default as stats } from './stats';
......
...@@ -2,6 +2,8 @@ import type { Feature } from './types'; ...@@ -2,6 +2,8 @@ import type { Feature } from './types';
import type { RollupType } from 'types/client/rollup'; import type { RollupType } from 'types/client/rollup';
import { ROLLUP_TYPES } from 'types/client/rollup'; import { ROLLUP_TYPES } from 'types/client/rollup';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import { getEnvValue } from '../utils'; import { getEnvValue } from '../utils';
const type = (() => { const type = (() => {
...@@ -21,7 +23,7 @@ const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: s ...@@ -21,7 +23,7 @@ const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: s
title, title,
isEnabled: true, isEnabled: true,
type, type,
L1BaseUrl, L1BaseUrl: stripTrailingSlash(L1BaseUrl),
L2WithdrawalUrl, L2WithdrawalUrl,
}); });
} }
......
import type { Feature } from './types';
import { getEnvValue } from '../utils';
import marketplace from './marketplace';
const title = 'Save on gas with GasHawk';
const config: Feature<{
apiUrlTemplate: string;
}> = (() => {
if (getEnvValue('NEXT_PUBLIC_SAVE_ON_GAS_ENABLED') === 'true' && marketplace.isEnabled) {
return Object.freeze({
title,
isEnabled: true,
apiUrlTemplate: 'https://core.gashawk.io/apiv2/stats/address/<address>/savingsPotential/0x1',
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
import type { ContractCodeIde } from 'types/client/contract'; import type { ContractCodeIde } from 'types/client/contract';
import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId, type NavigationLayout } from 'types/client/navigation'; import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId, type NavigationLayout } from 'types/client/navigation';
import type { ChainIndicatorId } from 'types/homepage'; import { HOME_STATS_WIDGET_IDS, type ChainIndicatorId, type HeroBannerConfig, type HomeStatsWidgetId } from 'types/homepage';
import type { NetworkExplorer } from 'types/networks'; import type { NetworkExplorer } from 'types/networks';
import type { ColorThemeId } from 'types/settings'; import type { ColorThemeId } from 'types/settings';
import type { FontFamily } from 'types/ui';
import { COLOR_THEMES } from 'lib/settings/colorTheme'; import { COLOR_THEMES } from 'lib/settings/colorTheme';
import * as features from './features';
import * as views from './ui/views'; import * as views from './ui/views';
import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from './utils'; import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from './utils';
...@@ -24,6 +26,22 @@ const hiddenLinks = (() => { ...@@ -24,6 +26,22 @@ const hiddenLinks = (() => {
return result; return result;
})(); })();
const homePageStats: Array<HomeStatsWidgetId> = (() => {
const parsedValue = parseEnvJson<Array<HomeStatsWidgetId>>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_STATS'));
if (!Array.isArray(parsedValue)) {
const rollupFeature = features.rollup;
if (rollupFeature.isEnabled && [ 'zkEvm', 'zkSync', 'arbitrum' ].includes(rollupFeature.type)) {
return [ 'latest_batch', 'average_block_time', 'total_txs', 'wallet_addresses', 'gas_tracker' ];
}
return [ 'total_blocks', 'average_block_time', 'total_txs', 'wallet_addresses', 'gas_tracker' ];
}
return parsedValue.filter((item) => HOME_STATS_WIDGET_IDS.includes(item));
})();
const highlightedRoutes = (() => { const highlightedRoutes = (() => {
const parsedValue = parseEnvJson<Array<NavigationLinkId>>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES')); const parsedValue = parseEnvJson<Array<NavigationLinkId>>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES'));
return Array.isArray(parsedValue) ? parsedValue : []; return Array.isArray(parsedValue) ? parsedValue : [];
...@@ -34,9 +52,6 @@ const defaultColorTheme = (() => { ...@@ -34,9 +52,6 @@ const defaultColorTheme = (() => {
return COLOR_THEMES.find((theme) => theme.id === envValue); return COLOR_THEMES.find((theme) => theme.id === envValue);
})(); })();
// eslint-disable-next-line max-len
const HOMEPAGE_PLATE_BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)';
const UI = Object.freeze({ const UI = Object.freeze({
navigation: { navigation: {
logo: { logo: {
...@@ -60,11 +75,13 @@ const UI = Object.freeze({ ...@@ -60,11 +75,13 @@ const UI = Object.freeze({
}, },
homepage: { homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_CHARTS')) || [], charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_CHARTS')) || [],
stats: homePageStats,
heroBanner: parseEnvJson<HeroBannerConfig>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG')),
// !!! DEPRECATED !!!
plate: { plate: {
background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND') || HOMEPAGE_PLATE_BACKGROUND_DEFAULT, background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND'),
textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR') || 'white', textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR'),
}, },
showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true,
}, },
views, views,
indexingAlert: { indexingAlert: {
...@@ -88,6 +105,10 @@ const UI = Object.freeze({ ...@@ -88,6 +105,10 @@ const UI = Object.freeze({
colorTheme: { colorTheme: {
'default': defaultColorTheme, 'default': defaultColorTheme,
}, },
fonts: {
heading: parseEnvJson<FontFamily>(getEnvValue('NEXT_PUBLIC_FONT_FAMILY_HEADING')),
body: parseEnvJson<FontFamily>(getEnvValue('NEXT_PUBLIC_FONT_FAMILY_BODY')),
},
maxContentWidth: getEnvValue('NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED') === 'false' ? false : true, maxContentWidth: getEnvValue('NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED') === 'false' ? false : true,
}); });
......
...@@ -37,3 +37,4 @@ NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-c ...@@ -37,3 +37,4 @@ NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','current_epoch']
\ No newline at end of file
...@@ -49,7 +49,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH ...@@ -49,7 +49,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/eth/pools'}},{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'blockchair','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/blockchair.png','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'sentio','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/sentio.png','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/0xPPL.png','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ] NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/eth/pools'}},{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'blockchair','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/blockchair.png','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'sentio','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/sentio.png','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/0xPPL.png','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ]
NEXT_PUBLIC_NETWORK_ID=1 NEXT_PUBLIC_NETWORK_ID=1
NEXT_PUBLIC_NETWORK_NAME=Ethereum NEXT_PUBLIC_NETWORK_NAME=Ethereum
NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.llamarpc.com NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.drpc.org
NEXT_PUBLIC_NETWORK_SHORT_NAME=Ethereum NEXT_PUBLIC_NETWORK_SHORT_NAME=Ethereum
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
...@@ -62,3 +62,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com ...@@ -62,3 +62,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true
...@@ -24,7 +24,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ ...@@ -24,7 +24,6 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config # ui config
## homepage ## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND= NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=
## sidebar ## sidebar
NEXT_PUBLIC_NETWORK_LOGO= NEXT_PUBLIC_NETWORK_LOGO=
......
# Set of ENVs for OP Celestia Raspberry network explorer
# https://opcelestia-raspberry.gelatoscout.com
# This is an auto-generated file. To update all values, run "yarn preset:sync --name=optimism_celestia"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'721628','width':'728','height':'90'}
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'721627','width':'300','height':'100'}
NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler
NEXT_PUBLIC_AD_TEXT_PROVIDER=none
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=opcelestia-raspberry.gelatoscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
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'}]
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/opcelestia-raspberry.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x0f5b54de81848d8d8baa02c69030037218a2b4df622d64a2a429e11721606656
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(255, 0, 0, 1)
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
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_ID=123420111
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_NETWORK_NAME=OP Celestia Raspberry
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.opcelestia-raspberry.gelato.digital
NEXT_PUBLIC_NETWORK_SHORT_NAME=opcelestia-raspberry
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.gelato.network/bridge/opcelestia-raspberry
NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_STATS_API_HOST=https://stats-opcelestia-raspberry.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_WEB3_WALLETS=none
\ No newline at end of file
...@@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ ...@@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/
# ui config # ui config
## homepage ## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
## sidebar ## sidebar
## footer ## footer
NEXT_PUBLIC_GIT_TAG=v1.0.11 NEXT_PUBLIC_GIT_TAG=v1.0.11
......
...@@ -43,3 +43,4 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout ...@@ -43,3 +43,4 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward','nonce'] NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward','nonce']
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','btc_locked']
\ No newline at end of file
...@@ -18,6 +18,12 @@ echo "window.__envs = {" >> $output_file; ...@@ -18,6 +18,12 @@ echo "window.__envs = {" >> $output_file;
# Iterate through all environment variables # Iterate through all environment variables
for var in $(env | grep '^NEXT_PUBLIC_' | cut -d= -f1); do for var in $(env | grep '^NEXT_PUBLIC_' | cut -d= -f1); do
# Skip variables that start with NEXT_PUBLIC_VERCEL. Vercel injects these
# and they can cause runtime errors, particularly when commit messages wrap lines.
if [[ $var == NEXT_PUBLIC_VERCEL* ]]; then
continue
fi
# Get the value of the variable # Get the value of the variable
value="${!var}" value="${!var}"
......
...@@ -20,6 +20,7 @@ async function run() { ...@@ -20,6 +20,7 @@ async function run() {
return result; return result;
}, {} as Record<string, string>); }, {} as Record<string, string>);
printDeprecationWarning(appEnvs);
await checkPlaceholdersCongruity(appEnvs); await checkPlaceholdersCongruity(appEnvs);
await validateEnvs(appEnvs); await validateEnvs(appEnvs);
...@@ -135,3 +136,15 @@ function getEnvsPlaceholders(filePath: string): Promise<Array<string>> { ...@@ -135,3 +136,15 @@ function getEnvsPlaceholders(filePath: string): Promise<Array<string>> {
}); });
}); });
} }
function printDeprecationWarning(envsMap: Record<string, string>) {
if (
envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR ||
envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND
) {
console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗');
// eslint-disable-next-line max-len
console.warn('The NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR and NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND variables are now deprecated and will be removed in the next release. Please migrate to the NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG variable.');
console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n');
}
}
...@@ -29,10 +29,11 @@ import type { ValidatorsChainType } from '../../../types/client/validators'; ...@@ -29,10 +29,11 @@ import type { ValidatorsChainType } from '../../../types/client/validators';
import type { WalletType } from '../../../types/client/wallets'; import type { WalletType } from '../../../types/client/wallets';
import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
import { CHAIN_INDICATOR_IDS } from '../../../types/homepage'; import { CHAIN_INDICATOR_IDS, HOME_STATS_WIDGET_IDS } from '../../../types/homepage';
import type { ChainIndicatorId } from '../../../types/homepage'; import type { ChainIndicatorId, HeroBannerButtonState, HeroBannerConfig, HomeStatsWidgetId } from '../../../types/homepage';
import { type NetworkVerificationTypeEnvs, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks'; import { type NetworkVerificationTypeEnvs, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks';
import { COLOR_THEME_IDS } from '../../../types/settings'; import { COLOR_THEME_IDS } from '../../../types/settings';
import type { FontFamily } from '../../../types/ui';
import type { AddressViewId } from '../../../types/views/address'; import type { AddressViewId } from '../../../types/views/address';
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
import { BLOCK_FIELDS_IDS } from '../../../types/views/block'; import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
...@@ -390,6 +391,34 @@ const navItemExternalSchema: yup.ObjectSchema<NavItemExternal> = yup ...@@ -390,6 +391,34 @@ const navItemExternalSchema: yup.ObjectSchema<NavItemExternal> = yup
url: yup.string().test(urlTest).required(), url: yup.string().test(urlTest).required(),
}); });
const fontFamilySchema: yup.ObjectSchema<FontFamily> = yup
.object()
.transform(replaceQuotes)
.json()
.shape({
name: yup.string().required(),
url: yup.string().test(urlTest).required(),
});
const heroBannerButtonStateSchema: yup.ObjectSchema<HeroBannerButtonState> = yup.object({
background: yup.array().max(2).of(yup.string()),
text_color: yup.array().max(2).of(yup.string()),
});
const heroBannerSchema: yup.ObjectSchema<HeroBannerConfig> = yup.object()
.transform(replaceQuotes)
.json()
.shape({
background: yup.array().max(2).of(yup.string()),
text_color: yup.array().max(2).of(yup.string()),
border: yup.array().max(2).of(yup.string()),
button: yup.object({
_default: heroBannerButtonStateSchema,
_hover: heroBannerButtonStateSchema,
_selected: heroBannerButtonStateSchema,
}),
});
const footerLinkSchema: yup.ObjectSchema<CustomLink> = yup const footerLinkSchema: yup.ObjectSchema<CustomLink> = yup
.object({ .object({
text: yup.string().required(), text: yup.string().required(),
...@@ -538,9 +567,30 @@ const schema = yup ...@@ -538,9 +567,30 @@ const schema = yup
.transform(replaceQuotes) .transform(replaceQuotes)
.json() .json()
.of(yup.string<ChainIndicatorId>().oneOf(CHAIN_INDICATOR_IDS)), .of(yup.string<ChainIndicatorId>().oneOf(CHAIN_INDICATOR_IDS)),
NEXT_PUBLIC_HOMEPAGE_STATS: yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string<HomeStatsWidgetId>().oneOf(HOME_STATS_WIDGET_IDS)),
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(), NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(),
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(), NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(),
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME: yup.boolean(), NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG: yup
.mixed()
.test(
'shape',
(ctx) => {
try {
heroBannerSchema.validateSync(ctx.originalValue);
throw new Error('Unknown validation error');
} catch (error: unknown) {
const message = typeof error === 'object' && error !== null && 'errors' in error && Array.isArray(error.errors) ? error.errors.join(', ') : '';
return 'Invalid schema were provided for NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG' + (message ? `: ${ message }` : '');
}
},
(data) => {
const isUndefined = data === undefined;
return isUndefined || heroBannerSchema.isValidSync(data);
}),
// b. sidebar // b. sidebar
NEXT_PUBLIC_FEATURED_NETWORKS: yup NEXT_PUBLIC_FEATURED_NETWORKS: yup
...@@ -634,6 +684,18 @@ const schema = yup ...@@ -634,6 +684,18 @@ const schema = yup
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(),
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(),
NEXT_PUBLIC_COLOR_THEME_DEFAULT: yup.string().oneOf(COLOR_THEME_IDS), NEXT_PUBLIC_COLOR_THEME_DEFAULT: yup.string().oneOf(COLOR_THEME_IDS),
NEXT_PUBLIC_FONT_FAMILY_HEADING: yup
.mixed()
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_FONT_FAMILY_HEADING', (data) => {
const isUndefined = data === undefined;
return isUndefined || fontFamilySchema.isValidSync(data);
}),
NEXT_PUBLIC_FONT_FAMILY_BODY: yup
.mixed()
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_FONT_FAMILY_BODY', (data) => {
const isUndefined = data === undefined;
return isUndefined || fontFamilySchema.isValidSync(data);
}),
NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED: yup.boolean(), NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED: yup.boolean(),
// 5. Features configuration // 5. Features configuration
...@@ -740,6 +802,7 @@ const schema = yup ...@@ -740,6 +802,7 @@ const schema = yup
value => value === undefined, value => value === undefined,
), ),
}), }),
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED: yup.boolean(),
// 6. External services envs // 6. External services envs
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),
......
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none
NEXT_PUBLIC_API_SPEC_URL=none NEXT_PUBLIC_API_SPEC_URL=none
NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none
NEXT_PUBLIC_HOMEPAGE_STATS=[]
\ No newline at end of file
...@@ -28,15 +28,18 @@ NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true ...@@ -28,15 +28,18 @@ NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true
NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps'] NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps']
NEXT_PUBLIC_NAVIGATION_LAYOUT=horizontal NEXT_PUBLIC_NAVIGATION_LAYOUT=horizontal
NEXT_PUBLIC_FONT_FAMILY_HEADING={'name':'Montserrat','url':'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap'}
NEXT_PUBLIC_FONT_FAMILY_BODY={'name':'Raleway','url':'https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600;700&display=swap'}
NEXT_PUBLIC_FOOTER_LINKS=https://example.com NEXT_PUBLIC_FOOTER_LINKS=https://example.com
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false
NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED=false NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED=false
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','current_epoch']
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='#fff' NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='#fff'
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgb(255, 145, 0)' NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgb(255, 145, 0)'
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['lightpink'],'text_color':['deepskyblue','white'],'border':['3px solid black']}
NEXT_PUBLIC_GAS_TRACKER_ENABLED=true NEXT_PUBLIC_GAS_TRACKER_ENABLED=true
NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei'] NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei']
NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_IS_TESTNET=true
...@@ -82,3 +85,4 @@ NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability ...@@ -82,3 +85,4 @@ NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','url':'https://example.com'}] NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','url':'https://example.com'}]
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'dapp_id': 'smol-refuel', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&utm_medium=address&disableBridges=true', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'dapp_id': 'smol-refuel', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&utm_medium=address&disableBridges=true', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'}
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true
...@@ -33,9 +33,12 @@ CONFIG_TEMPLATE_FILE="config.template.json" ...@@ -33,9 +33,12 @@ CONFIG_TEMPLATE_FILE="config.template.json"
# Path to the generated config JSON file # Path to the generated config JSON file
CONFIG_FILE="config.json" CONFIG_FILE="config.json"
# Escape special characters in MASTER_URL for sed
ESCAPED_MASTER_URL=$(printf '%s\n' "$MASTER_URL" | sed -e 's/[\/&]/\\&/g')
# Replace <api_key> and <master_url> placeholders in the JSON template file # Replace <api_key> and <master_url> placeholders in the JSON template file
API_KEY_VALUE="$FAVICON_GENERATOR_API_KEY" API_KEY_VALUE="$FAVICON_GENERATOR_API_KEY"
sed -e "s|<api_key>|$API_KEY_VALUE|" -e "s|<master_url>|$MASTER_URL|" "$CONFIG_TEMPLATE_FILE" > "$CONFIG_FILE" sed -e "s|<api_key>|$API_KEY_VALUE|" -e "s|<master_url>|$ESCAPED_MASTER_URL|" "$CONFIG_TEMPLATE_FILE" > "$CONFIG_FILE"
# Make the API POST request with JSON data from the config file # Make the API POST request with JSON data from the config file
echo "⏳ Making request to API..." echo "⏳ Making request to API..."
......
...@@ -4,7 +4,7 @@ imagePullSecrets: ...@@ -4,7 +4,7 @@ imagePullSecrets:
- name: regcred - name: regcred
config: config:
network: network:
id: 11155111 id: "11155111"
name: Blockscout name: Blockscout
shortname: Blockscout shortname: Blockscout
currency: currency:
......
...@@ -10,3 +10,4 @@ ...@@ -10,3 +10,4 @@
| 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_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.12.0 | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | | NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.12.0 | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL |
| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.24.0 | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | | NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.24.0 | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS |
| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | v1.0.x+ | v1.35.0 | Replaces by NEXT_PUBLIC_HOMEPAGE_STATS
\ No newline at end of file
This diff is collapsed.
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" fill="url(#a)"/>
<g clip-path="url(#b)" fill="#fff" fill-opacity=".95">
<path d="M15.763 12.826c.119-.243.178-.365.158-.462a.292.292 0 0 0-.148-.199c-.088-.047-.244-.02-.554.033a6.409 6.409 0 0 1-5.632-1.786 6.409 6.409 0 0 1-1.785-5.631c.053-.31.08-.466.033-.554a.292.292 0 0 0-.199-.149c-.098-.02-.219.04-.462.159a6.417 6.417 0 1 0 8.589 8.589Z"/>
<path d="M15.9 10.817c.152-.054.229-.082.31-.152a.686.686 0 0 0 .163-.228c.04-.1.04-.183.043-.35a6.398 6.398 0 0 0-1.879-4.624 6.398 6.398 0 0 0-4.624-1.88c-.167.003-.25.004-.35.044a.685.685 0 0 0-.229.163c-.07.081-.097.158-.151.31a5.25 5.25 0 0 0 6.717 6.717Z"/>
</g>
<defs>
<linearGradient id="a" x1="17.5" y1="2" x2="0" y2="20" gradientUnits="userSpaceOnUse">
<stop stop-color="#196E41"/>
<stop offset="1" stop-color="#092E1B"/>
</linearGradient>
<clipPath id="b">
<rect x="3" y="3" width="14" height="14" rx="7" fill="#fff"/>
</clipPath>
</defs>
</svg>
import { getAddress } from 'viem';
export default function getCheckedSummedAddress(address: string): string {
try {
return getAddress(address);
} catch (error) {
return address;
}
}
...@@ -31,7 +31,6 @@ import type { ...@@ -31,7 +31,6 @@ import type {
AddressNFTsResponse, AddressNFTsResponse,
AddressCollectionsResponse, AddressCollectionsResponse,
AddressNFTTokensFilter, AddressNFTTokensFilter,
AddressCoinBalanceHistoryChartOld,
AddressMudTables, AddressMudTables,
AddressMudTablesFilter, AddressMudTablesFilter,
AddressMudRecords, AddressMudRecords,
...@@ -39,7 +38,7 @@ import type { ...@@ -39,7 +38,7 @@ import type {
AddressMudRecordsSorting, AddressMudRecordsSorting,
AddressMudRecord, AddressMudRecord,
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses'; import type { AddressesResponse, AddressesMetadataSearchResult, AddressesMetadataSearchFilters } from 'types/api/addresses';
import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata';
import type { import type {
ArbitrumL2MessagesResponse, ArbitrumL2MessagesResponse,
...@@ -62,7 +61,7 @@ import type { ...@@ -62,7 +61,7 @@ import type {
BlockEpochElectionRewardDetailsResponse, BlockEpochElectionRewardDetailsResponse,
} from 'types/api/block'; } from 'types/api/block';
import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts';
import type { BackendVersionConfig } from 'types/api/configs'; import type { BackendVersionConfig, CsvExportConfig } from 'types/api/configs';
import type { import type {
SmartContract, SmartContract,
SmartContractVerificationConfigRaw, SmartContractVerificationConfigRaw,
...@@ -86,6 +85,9 @@ import type { ...@@ -86,6 +85,9 @@ import type {
OptimisticL2TxnBatchesResponse, OptimisticL2TxnBatchesResponse,
OptimisticL2WithdrawalsResponse, OptimisticL2WithdrawalsResponse,
OptimisticL2DisputeGamesResponse, OptimisticL2DisputeGamesResponse,
OptimismL2TxnBatch,
OptimismL2BatchTxs,
OptimismL2BatchBlocks,
} from 'types/api/optimisticL2'; } from 'types/api/optimisticL2';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search'; import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
...@@ -159,26 +161,26 @@ export const RESOURCES = { ...@@ -159,26 +161,26 @@ export const RESOURCES = {
path: '/api/account/v2/email/resend', path: '/api/account/v2/email/resend',
}, },
custom_abi: { custom_abi: {
path: '/api/account/v2/user/custom_abis/:id?', path: '/api/account/v2/user/custom_abis{/:id}',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
}, },
watchlist: { watchlist: {
path: '/api/account/v2/user/watchlist/:id?', path: '/api/account/v2/user/watchlist{/:id}',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
filterFields: [ ], filterFields: [ ],
}, },
private_tags_address: { private_tags_address: {
path: '/api/account/v2/user/tags/address/:id?', path: '/api/account/v2/user/tags/address{/:id}',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
filterFields: [ ], filterFields: [ ],
}, },
private_tags_tx: { private_tags_tx: {
path: '/api/account/v2/user/tags/transaction/:id?', path: '/api/account/v2/user/tags/transaction{/:id}',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
filterFields: [ ], filterFields: [ ],
}, },
api_keys: { api_keys: {
path: '/api/account/v2/user/api_keys/:id?', path: '/api/account/v2/user/api_keys{/:id}',
pathParams: [ 'id' as const ], pathParams: [ 'id' as const ],
}, },
...@@ -208,7 +210,7 @@ export const RESOURCES = { ...@@ -208,7 +210,7 @@ export const RESOURCES = {
}, },
token_info_applications: { token_info_applications: {
path: '/api/v1/chains/:chainId/token-info-submissions/:id?', path: '/api/v1/chains/:chainId/token-info-submissions{/:id}',
pathParams: [ 'chainId' as const, 'id' as const ], pathParams: [ 'chainId' as const, 'id' as const ],
endpoint: getFeaturePayload(config.features.addressVerification)?.api.endpoint, endpoint: getFeaturePayload(config.features.addressVerification)?.api.endpoint,
basePath: getFeaturePayload(config.features.addressVerification)?.api.basePath, basePath: getFeaturePayload(config.features.addressVerification)?.api.basePath,
...@@ -419,6 +421,10 @@ export const RESOURCES = { ...@@ -419,6 +421,10 @@ export const RESOURCES = {
path: '/api/v2/addresses/', path: '/api/v2/addresses/',
filterFields: [ ], filterFields: [ ],
}, },
addresses_metadata_search: {
path: '/api/v2/proxy/metadata/addresses',
filterFields: [ 'slug' as const, 'tag_type' as const ],
},
// ADDRESS // ADDRESS
address: { address: {
...@@ -679,12 +685,29 @@ export const RESOURCES = { ...@@ -679,12 +685,29 @@ export const RESOURCES = {
}, },
optimistic_l2_txn_batches: { optimistic_l2_txn_batches: {
path: '/api/v2/optimism/txn-batches', path: '/api/v2/optimism/batches',
filterFields: [], filterFields: [],
}, },
optimistic_l2_txn_batches_count: { optimistic_l2_txn_batches_count: {
path: '/api/v2/optimism/txn-batches/count', path: '/api/v2/optimism/batches/count',
},
optimistic_l2_txn_batch: {
path: '/api/v2/optimism/batches/:number',
pathParams: [ 'number' as const ],
},
optimistic_l2_txn_batch_txs: {
path: '/api/v2/transactions/optimism-batch/:number',
pathParams: [ 'number' as const ],
filterFields: [],
},
optimistic_l2_txn_batch_blocks: {
path: '/api/v2/blocks/optimism-batch/:number',
pathParams: [ 'number' as const ],
filterFields: [],
}, },
optimistic_l2_dispute_games: { optimistic_l2_dispute_games: {
...@@ -894,6 +917,9 @@ export const RESOURCES = { ...@@ -894,6 +917,9 @@ export const RESOURCES = {
config_backend_version: { config_backend_version: {
path: '/api/v2/config/backend-version', path: '/api/v2/config/backend-version',
}, },
config_csv_export: {
path: '/api/v2/config/csv-export',
},
// CSV EXPORT // CSV EXPORT
csv_export_token_holders: { csv_export_token_holders: {
...@@ -960,7 +986,7 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }> ...@@ -960,7 +986,7 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }>
export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_rewards' | export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_rewards' |
'txs_validated' | 'txs_pending' | 'txs_with_blobs' | 'txs_watchlist' | 'txs_execution_node' | 'txs_validated' | 'txs_pending' | 'txs_with_blobs' | 'txs_watchlist' | 'txs_execution_node' |
'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_state_changes' | 'tx_blobs' | 'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_state_changes' | 'tx_blobs' |
'addresses' | 'addresses' | 'addresses_metadata_search' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' | 'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' |
...@@ -968,7 +994,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward ...@@ -968,7 +994,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'token_instance_transfers' | 'token_instance_holders' | 'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' | 'verified_contracts' |
'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' | 'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' |
'optimistic_l2_dispute_games' | 'optimistic_l2_dispute_games' | 'optimistic_l2_txn_batch_txs' | 'optimistic_l2_txn_batch_blocks' |
'mud_worlds'| 'address_mud_tables' | 'address_mud_records' | 'mud_worlds'| 'address_mud_tables' | 'address_mud_records' |
'shibarium_deposits' | 'shibarium_withdrawals' | 'shibarium_deposits' | 'shibarium_withdrawals' |
'arbitrum_l2_messages' | 'arbitrum_l2_txn_batches' | 'arbitrum_l2_txn_batch_txs' | 'arbitrum_l2_txn_batch_blocks' | 'arbitrum_l2_messages' | 'arbitrum_l2_txn_batches' | 'arbitrum_l2_txn_batch_txs' | 'arbitrum_l2_txn_batch_blocks' |
...@@ -1034,6 +1060,7 @@ Q extends 'tx_state_changes' ? TxStateChanges : ...@@ -1034,6 +1060,7 @@ Q extends 'tx_state_changes' ? TxStateChanges :
Q extends 'tx_blobs' ? TxBlobs : Q extends 'tx_blobs' ? TxBlobs :
Q extends 'tx_interpretation' ? TxInterpretationResponse : Q extends 'tx_interpretation' ? TxInterpretationResponse :
Q extends 'addresses' ? AddressesResponse : Q extends 'addresses' ? AddressesResponse :
Q extends 'addresses_metadata_search' ? AddressesMetadataSearchResult :
Q extends 'address' ? Address : Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters : Q extends 'address_counters' ? AddressCounters :
Q extends 'address_tabs_counters' ? AddressTabsCounters : Q extends 'address_tabs_counters' ? AddressTabsCounters :
...@@ -1042,7 +1069,7 @@ Q extends 'address_internal_txs' ? AddressInternalTxsResponse : ...@@ -1042,7 +1069,7 @@ Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
Q extends 'address_token_transfers' ? AddressTokenTransferResponse : Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse : Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse : Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChartOld | AddressCoinBalanceHistoryChart : Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart :
Q extends 'address_logs' ? LogsResponseAddress : Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse : Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'address_nfts' ? AddressNFTsResponse : Q extends 'address_nfts' ? AddressNFTsResponse :
...@@ -1073,11 +1100,14 @@ Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse : ...@@ -1073,11 +1100,14 @@ Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse :
Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse : Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse :
Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse :
Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse : Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse :
Q extends 'optimistic_l2_txn_batches_count' ? number :
Q extends 'optimistic_l2_txn_batch' ? OptimismL2TxnBatch :
Q extends 'optimistic_l2_txn_batch_txs' ? OptimismL2BatchTxs :
Q extends 'optimistic_l2_txn_batch_blocks' ? OptimismL2BatchBlocks :
Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse : Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse :
Q extends 'optimistic_l2_output_roots_count' ? number : Q extends 'optimistic_l2_output_roots_count' ? number :
Q extends 'optimistic_l2_withdrawals_count' ? number : Q extends 'optimistic_l2_withdrawals_count' ? number :
Q extends 'optimistic_l2_deposits_count' ? number : Q extends 'optimistic_l2_deposits_count' ? number :
Q extends 'optimistic_l2_txn_batches_count' ? number :
Q extends 'optimistic_l2_dispute_games_count' ? number : Q extends 'optimistic_l2_dispute_games_count' ? number :
never; never;
// !!! IMPORTANT !!! // !!! IMPORTANT !!!
...@@ -1087,6 +1117,7 @@ never; ...@@ -1087,6 +1117,7 @@ never;
/* eslint-disable @typescript-eslint/indent */ /* eslint-disable @typescript-eslint/indent */
export type ResourcePayloadB<Q extends ResourceName> = export type ResourcePayloadB<Q extends ResourceName> =
Q extends 'config_backend_version' ? BackendVersionConfig : Q extends 'config_backend_version' ? BackendVersionConfig :
Q extends 'config_csv_export' ? CsvExportConfig :
Q extends 'address_metadata_info' ? AddressMetadataInfo : Q extends 'address_metadata_info' ? AddressMetadataInfo :
Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse : Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'blob' ? Blob : Q extends 'blob' ? Blob :
...@@ -1156,6 +1187,7 @@ Q extends 'txs_with_blobs' ? TTxsWithBlobsFilters : ...@@ -1156,6 +1187,7 @@ Q extends 'txs_with_blobs' ? TTxsWithBlobsFilters :
Q extends 'tx_token_transfers' ? TokenTransferFilters : Q extends 'tx_token_transfers' ? TokenTransferFilters :
Q extends 'token_transfers' ? TokenTransferFilters : Q extends 'token_transfers' ? TokenTransferFilters :
Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters :
Q extends 'addresses_metadata_search' ? AddressesMetadataSearchFilters :
Q extends 'address_token_transfers' ? AddressTokenTransferFilters : Q extends 'address_token_transfers' ? AddressTokenTransferFilters :
Q extends 'address_tokens' ? AddressTokensFilter : Q extends 'address_tokens' ? AddressTokensFilter :
Q extends 'address_nfts' ? AddressNFTTokensFilter : Q extends 'address_nfts' ? AddressNFTTokensFilter :
......
...@@ -12,6 +12,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -12,6 +12,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/block/countdown': 'Regular page', '/block/countdown': 'Regular page',
'/block/countdown/[height]': 'Regular page', '/block/countdown/[height]': 'Regular page',
'/accounts': 'Root page', '/accounts': 'Root page',
'/accounts/label/[slug]': 'Root page',
'/address/[hash]': 'Regular page', '/address/[hash]': 'Regular page',
'/verified-contracts': 'Root page', '/verified-contracts': 'Root page',
'/contract-verification': 'Root page', '/contract-verification': 'Root page',
......
...@@ -16,6 +16,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -16,6 +16,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/block/countdown': DEFAULT_TEMPLATE, '/block/countdown': DEFAULT_TEMPLATE,
'/block/countdown/[height]': DEFAULT_TEMPLATE, '/block/countdown/[height]': DEFAULT_TEMPLATE,
'/accounts': DEFAULT_TEMPLATE, '/accounts': DEFAULT_TEMPLATE,
'/accounts/label/[slug]': DEFAULT_TEMPLATE,
'/address/[hash]': 'View the account balance, transactions, and other data for %hash% on the %network_title%', '/address/[hash]': 'View the account balance, transactions, and other data for %hash% on the %network_title%',
'/verified-contracts': DEFAULT_TEMPLATE, '/verified-contracts': DEFAULT_TEMPLATE,
'/contract-verification': DEFAULT_TEMPLATE, '/contract-verification': DEFAULT_TEMPLATE,
......
...@@ -12,6 +12,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -12,6 +12,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/block/countdown': '%network_name% block countdown index', '/block/countdown': '%network_name% block countdown index',
'/block/countdown/[height]': '%network_name% block %height% countdown', '/block/countdown/[height]': '%network_name% block %height% countdown',
'/accounts': '%network_name% top accounts', '/accounts': '%network_name% top accounts',
'/accounts/label/[slug]': '%network_name% addresses search by label',
'/address/[hash]': '%network_name% address details for %hash%', '/address/[hash]': '%network_name% address details for %hash%',
'/verified-contracts': 'Verified %network_name% contracts lookup - %network_name% explorer', '/verified-contracts': 'Verified %network_name% contracts lookup - %network_name% explorer',
'/contract-verification': '%network_name% verify contract', '/contract-verification': '%network_name% verify contract',
......
...@@ -10,6 +10,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -10,6 +10,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/block/countdown': 'Block countdown search', '/block/countdown': 'Block countdown search',
'/block/countdown/[height]': 'Block countdown', '/block/countdown/[height]': 'Block countdown',
'/accounts': 'Top accounts', '/accounts': 'Top accounts',
'/accounts/label/[slug]': 'Addresses search by label',
'/address/[hash]': 'Address details', '/address/[hash]': 'Address details',
'/verified-contracts': 'Verified contracts', '/verified-contracts': 'Verified contracts',
'/contract-verification': 'Contract verification', '/contract-verification': 'Contract verification',
......
...@@ -35,7 +35,10 @@ const wagmiConfig = (() => { ...@@ -35,7 +35,10 @@ const wagmiConfig = (() => {
url: config.app.baseUrl, url: config.app.baseUrl,
icons: [ config.UI.navigation.icon.default ].filter(Boolean), icons: [ config.UI.navigation.icon.default ].filter(Boolean),
}, },
enableEmail: true, auth: {
email: true,
socials: [],
},
ssr: true, ssr: true,
batch: { multicall: { wait: 100 } }, batch: { multicall: { wait: 100 } },
}); });
......
...@@ -43,7 +43,7 @@ export const verified: SmartContract = { ...@@ -43,7 +43,7 @@ export const verified: SmartContract = {
file_path: '', file_path: '',
additional_sources: [], additional_sources: [],
verified_twin_address_hash: null, verified_twin_address_hash: null,
minimal_proxy_address_hash: null, proxy_type: null,
}; };
export const certified: SmartContract = { export const certified: SmartContract = {
...@@ -85,7 +85,7 @@ export const withProxyAddress: SmartContract = { ...@@ -85,7 +85,7 @@ export const withProxyAddress: SmartContract = {
...verified, ...verified,
is_verified: false, is_verified: false,
verified_twin_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', verified_twin_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8',
minimal_proxy_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', proxy_type: 'eip1967',
}; };
export const selfDestructed: SmartContract = { export const selfDestructed: SmartContract = {
...@@ -133,7 +133,7 @@ export const nonVerified: SmartContract = { ...@@ -133,7 +133,7 @@ export const nonVerified: SmartContract = {
additional_sources: [], additional_sources: [],
external_libraries: null, external_libraries: null,
verified_twin_address_hash: null, verified_twin_address_hash: null,
minimal_proxy_address_hash: null, proxy_type: null,
language: null, language: null,
license_type: null, license_type: null,
}; };
export const txnBatchesData = {
items: [
{
l1_tx_hashes: [
'0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5',
'0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d',
],
l1_timestamp: '2023-02-24T10:16:12.000000Z',
l2_block_number: 5902836,
tx_count: 0,
},
{
l1_tx_hashes: [
'0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa',
],
l1_timestamp: '2023-02-24T10:16:00.000000Z',
l2_block_number: 5902835,
tx_count: 0,
},
{
l1_tx_hashes: [
'0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8',
],
l1_timestamp: '2023-02-24T10:16:00.000000Z',
l2_block_number: 5902834,
tx_count: 0,
},
],
next_page_params: {
block_number: 5902834,
items_count: 50,
},
};
import type {
OptimismL2TxnBatchTypeCallData,
OptimismL2TxnBatchTypeCelestia,
OptimismL2TxnBatchTypeEip4844,
OptimisticL2TxnBatchesResponse,
} from 'types/api/optimisticL2';
export const txnBatchesData: OptimisticL2TxnBatchesResponse = {
items: [
{
batch_data_container: 'in_blob4844',
internal_id: 260998,
l1_timestamp: '2022-11-10T11:29:11.000000Z',
l1_tx_hashes: [
'0x9553351f6bd1577f4e782738c087be08697fb11f3b91745138d71ba166d62c3b',
],
l2_block_end: 124882074,
l2_block_start: 124881833,
tx_count: 4011,
},
{
batch_data_container: 'in_calldata',
internal_id: 260997,
l1_timestamp: '2022-11-03T11:20:59.000000Z',
l1_tx_hashes: [
'0x80f5fba70d5685bc2b70df836942e892b24afa7bba289a2fac0ca8f4d554cc72',
],
l2_block_end: 124881832,
l2_block_start: 124881613,
tx_count: 4206,
},
{
internal_id: 260996,
l1_timestamp: '2024-09-03T11:14:23.000000Z',
l1_tx_hashes: [
'0x39f4c46cae57bae936acb9159e367794f41f021ed3788adb80ad93830edb5f22',
],
l2_block_end: 124881612,
l2_block_start: 124881380,
tx_count: 4490,
},
],
next_page_params: {
id: 5902834,
items_count: 50,
},
};
export const txnBatchTypeCallData: OptimismL2TxnBatchTypeCallData = {
batch_data_container: 'in_calldata',
internal_id: 309123,
l1_timestamp: '2022-08-10T10:30:24.000000Z',
l1_tx_hashes: [
'0x478c45f182631ae6f7249d40f31fdac36f41d88caa2e373fba35340a7345ca67',
],
l2_block_end: 10146784,
l2_block_start: 10145379,
tx_count: 1608,
};
export const txnBatchTypeCelestia: OptimismL2TxnBatchTypeCelestia = {
batch_data_container: 'in_celestia',
blobs: [
{
commitment: '0x39c18c21c6b127d58809b8d3b5931472421f9b51532959442f53038f10b78f2a',
height: 2584868,
l1_timestamp: '2024-08-28T16:51:12.000000Z',
l1_transaction_hash: '0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24',
namespace: '0x00000000000000000000000000000000000000000008e5f679bf7116cb',
},
],
internal_id: 309667,
l1_timestamp: '2022-08-28T16:51:12.000000Z',
l1_tx_hashes: [
'0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24',
],
l2_block_end: 10935879,
l2_block_start: 10934514,
tx_count: 1574,
};
export const txnBatchTypeEip4844: OptimismL2TxnBatchTypeEip4844 = {
batch_data_container: 'in_blob4844',
blobs: [
{
hash: '0x012a4f0c6db6bce9d3d357b2bf847764320bcb0107ab318f3a532f637bc60dfe',
l1_timestamp: '2022-08-23T03:59:12.000000Z',
l1_transaction_hash: '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a',
},
{
hash: '0x01d1097cce23229931afbc2fd1cf0d707da26df7b39cef1c542276ae718de4f6',
l1_timestamp: '2022-08-23T03:59:12.000000Z',
l1_transaction_hash: '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a',
},
],
internal_id: 2538459,
l1_timestamp: '2022-08-23T03:59:12.000000Z',
l1_tx_hashes: [
'0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a',
],
l2_block_end: 16291502,
l2_block_start: 16291373,
tx_count: 704,
};
...@@ -72,6 +72,7 @@ export const withoutGasInfo: HomeStats = { ...@@ -72,6 +72,7 @@ export const withoutGasInfo: HomeStats = {
export const withSecondaryCoin: HomeStats = { export const withSecondaryCoin: HomeStats = {
...base, ...base,
secondary_coin_price: '3.398', secondary_coin_price: '3.398',
secondary_coin_image: 'http://localhost:3100/secondary_utia.jpg',
}; };
export const noChartData: HomeStats = { export const noChartData: HomeStats = {
......
...@@ -391,3 +391,8 @@ export const withRecipientNameTag = { ...@@ -391,3 +391,8 @@ export const withRecipientNameTag = {
...withRecipientEns, ...withRecipientEns,
to: addressMock.withNameTag, to: addressMock.withNameTag,
}; };
export const withRecipientContract = {
...withRecipientEns,
to: addressMock.contract,
};
...@@ -6,6 +6,7 @@ function generateCspPolicy() { ...@@ -6,6 +6,7 @@ function generateCspPolicy() {
descriptors.app(), descriptors.app(),
descriptors.ad(), descriptors.ad(),
descriptors.cloudFlare(), descriptors.cloudFlare(),
descriptors.gasHawk(),
descriptors.googleAnalytics(), descriptors.googleAnalytics(),
descriptors.googleFonts(), descriptors.googleFonts(),
descriptors.googleReCaptcha(), descriptors.googleReCaptcha(),
......
...@@ -30,6 +30,18 @@ const getCspReportUrl = () => { ...@@ -30,6 +30,18 @@ const getCspReportUrl = () => {
} }
}; };
const externalFontsDomains = (() => {
try {
return [
config.UI.fonts.heading?.url,
config.UI.fonts.body?.url,
]
.filter(Boolean)
.map((urlString) => new URL(urlString))
.map((url) => url.hostname);
} catch (error) {}
})();
export function app(): CspDev.DirectiveDescriptor { export function app(): CspDev.DirectiveDescriptor {
return { return {
'default-src': [ 'default-src': [
...@@ -72,8 +84,9 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -72,8 +84,9 @@ export function app(): CspDev.DirectiveDescriptor {
// https://github.com/vercel/next.js/issues/14221#issuecomment-657258278 // https://github.com/vercel/next.js/issues/14221#issuecomment-657258278
config.app.isDev ? KEY_WORDS.UNSAFE_EVAL : '', config.app.isDev ? KEY_WORDS.UNSAFE_EVAL : '',
// hash of ColorModeScript // hash of ColorModeScript: system + dark
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'', '\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
'\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'',
], ],
'style-src': [ 'style-src': [
...@@ -115,6 +128,7 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -115,6 +128,7 @@ export function app(): CspDev.DirectiveDescriptor {
'font-src': [ 'font-src': [
KEY_WORDS.DATA, KEY_WORDS.DATA,
...MAIN_DOMAINS, ...MAIN_DOMAINS,
...(externalFontsDomains || []),
], ],
'object-src': [ 'object-src': [
......
import type CspDev from 'csp-dev';
import config from 'configs/app';
const feature = config.features.saveOnGas;
export function gasHawk(): CspDev.DirectiveDescriptor {
if (!feature.isEnabled) {
return {};
}
const apiOrigin = (() => {
try {
const url = new URL(feature.apiUrlTemplate);
return url.origin;
} catch (error) {
return '';
}
})();
if (!apiOrigin) {
return {};
}
return {
'connect-src': [
apiOrigin,
],
};
}
export { ad } from './ad'; export { ad } from './ad';
export { app } from './app'; export { app } from './app';
export { cloudFlare } from './cloudFlare'; export { cloudFlare } from './cloudFlare';
export { gasHawk } from './gasHawk';
export { googleAnalytics } from './googleAnalytics'; export { googleAnalytics } from './googleAnalytics';
export { googleFonts } from './googleFonts'; export { googleFonts } from './googleFonts';
export { googleReCaptcha } from './googleReCaptcha'; export { googleReCaptcha } from './googleReCaptcha';
......
...@@ -14,6 +14,7 @@ export function walletConnect(): CspDev.DirectiveDescriptor { ...@@ -14,6 +14,7 @@ export function walletConnect(): CspDev.DirectiveDescriptor {
'*.web3modal.com', '*.web3modal.com',
'*.web3modal.org', '*.web3modal.org',
'*.walletconnect.com', '*.walletconnect.com',
'*.walletconnect.org',
'wss://relay.walletconnect.com', 'wss://relay.walletconnect.com',
'wss://www.walletlink.org', 'wss://www.walletlink.org',
], ],
......
...@@ -112,7 +112,7 @@ export const optimisticRollup: GetServerSideProps<Props> = async(context) => { ...@@ -112,7 +112,7 @@ export const optimisticRollup: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum' ]; const BATCH_ROLLUP_TYPES: Array<RollupType> = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic' ];
export const batch: GetServerSideProps<Props> = async(context) => { export const batch: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) { if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) {
return { return {
...@@ -204,6 +204,16 @@ export const accounts: GetServerSideProps<Props> = async(context) => { ...@@ -204,6 +204,16 @@ export const accounts: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const accountsLabelSearch: GetServerSideProps<Props> = async(context) => {
if (!config.features.addressMetadata.isEnabled || !context.query.tagType) {
return {
notFound: true,
};
}
return base(context);
};
export const userOps: GetServerSideProps<Props> = async(context) => { export const userOps: GetServerSideProps<Props> = async(context) => {
if (!config.features.userOps.isEnabled) { if (!config.features.userOps.isEnabled) {
return { return {
......
...@@ -13,6 +13,7 @@ declare module "nextjs-routes" { ...@@ -13,6 +13,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/account/verified-addresses"> | StaticRoute<"/account/verified-addresses">
| StaticRoute<"/account/watchlist"> | StaticRoute<"/account/watchlist">
| StaticRoute<"/accounts"> | StaticRoute<"/accounts">
| DynamicRoute<"/accounts/label/[slug]", { "slug": string }>
| DynamicRoute<"/address/[hash]/contract-verification", { "hash": string }> | DynamicRoute<"/address/[hash]/contract-verification", { "hash": string }>
| DynamicRoute<"/address/[hash]", { "hash": string }> | DynamicRoute<"/address/[hash]", { "hash": string }>
| StaticRoute<"/api/config"> | StaticRoute<"/api/config">
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"dev:preset": "./tools/scripts/dev.preset.sh", "dev:preset": "./tools/scripts/dev.preset.sh",
"dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js", "dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js",
"build": "next build", "build": "next build",
"build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && yarn svg:build-sprite && ./deploy/scripts/make_envs_script.sh && next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./", "build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./",
"start": "next start", "start": "next start",
"start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend:local", "start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend:local",
...@@ -49,22 +50,22 @@ ...@@ -49,22 +50,22 @@
"@metamask/providers": "^10.2.1", "@metamask/providers": "^10.2.1",
"@monaco-editor/react": "^4.4.6", "@monaco-editor/react": "^4.4.6",
"@next/bundle-analyzer": "14.2.3", "@next/bundle-analyzer": "14.2.3",
"@opentelemetry/auto-instrumentations-node": "^0.39.4", "@opentelemetry/auto-instrumentations-node": "0.43.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.45.1", "@opentelemetry/exporter-metrics-otlp-proto": "0.49.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0", "@opentelemetry/exporter-trace-otlp-http": "0.49.1",
"@opentelemetry/resources": "^1.18.0", "@opentelemetry/resources": "1.22.0",
"@opentelemetry/sdk-node": "^0.45.0", "@opentelemetry/sdk-node": "0.49.1",
"@opentelemetry/sdk-trace-node": "^1.18.0", "@opentelemetry/sdk-trace-node": "1.22.0",
"@opentelemetry/semantic-conventions": "^1.18.0", "@opentelemetry/semantic-conventions": "1.22.0",
"@sentry/cli": "^2.21.2", "@sentry/cli": "^2.21.2",
"@sentry/react": "7.24.0", "@sentry/react": "7.24.0",
"@sentry/tracing": "7.24.0", "@sentry/tracing": "7.24.0",
"@slise/embed-react": "^2.2.0", "@slise/embed-react": "^2.2.0",
"@tanstack/react-query": "^5.4.3", "@tanstack/react-query": "5.55.4",
"@tanstack/react-query-devtools": "^5.4.3", "@tanstack/react-query-devtools": "5.55.4",
"@types/papaparse": "^5.3.5", "@types/papaparse": "^5.3.5",
"@types/react-scroll": "^1.8.4", "@types/react-scroll": "^1.8.4",
"@web3modal/wagmi": "4.2.1", "@web3modal/wagmi": "5.1.7",
"airtable": "^0.12.2", "airtable": "^0.12.2",
"bignumber.js": "^9.1.0", "bignumber.js": "^9.1.0",
"blo": "^1.1.1", "blo": "^1.1.1",
...@@ -86,11 +87,11 @@ ...@@ -86,11 +87,11 @@
"magic-bytes.js": "1.8.0", "magic-bytes.js": "1.8.0",
"mixpanel-browser": "^2.47.0", "mixpanel-browser": "^2.47.0",
"monaco-editor": "^0.34.1", "monaco-editor": "^0.34.1",
"next": "14.2.3", "next": "14.2.9",
"nextjs-routes": "^1.0.8", "nextjs-routes": "^1.0.8",
"node-fetch": "^3.2.9", "node-fetch": "^3.2.9",
"papaparse": "^5.3.2", "papaparse": "^5.3.2",
"path-to-regexp": "^6.2.1", "path-to-regexp": "8.1.0",
"phoenix": "^1.6.15", "phoenix": "^1.6.15",
"pino-http": "^8.2.1", "pino-http": "^8.2.1",
"pino-pretty": "^9.1.1", "pino-pretty": "^9.1.1",
...@@ -109,8 +110,8 @@ ...@@ -109,8 +110,8 @@
"swagger-ui-react": "^5.9.0", "swagger-ui-react": "^5.9.0",
"use-font-face-observer": "^1.2.1", "use-font-face-observer": "^1.2.1",
"valibot": "0.38.0", "valibot": "0.38.0",
"viem": "2.10.9", "viem": "2.21.5",
"wagmi": "2.9.2", "wagmi": "2.12.10",
"xss": "^1.0.14" "xss": "^1.0.14"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -6,6 +6,7 @@ import React from 'react'; ...@@ -6,6 +6,7 @@ import React from 'react';
import logRequestFromBot from 'nextjs/utils/logRequestFromBot'; import logRequestFromBot from 'nextjs/utils/logRequestFromBot';
import * as serverTiming from 'nextjs/utils/serverTiming'; import * as serverTiming from 'nextjs/utils/serverTiming';
import config from 'configs/app';
import theme from 'theme/theme'; import theme from 'theme/theme';
import * as svgSprite from 'ui/shared/IconSvg'; import * as svgSprite from 'ui/shared/IconSvg';
...@@ -35,11 +36,11 @@ class MyDocument extends Document { ...@@ -35,11 +36,11 @@ class MyDocument extends Document {
<Head> <Head>
{ /* FONTS */ } { /* FONTS */ }
<link <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" href={ config.UI.fonts.heading?.url ?? 'https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap' }
rel="stylesheet" rel="stylesheet"
/> />
<link <link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" href={ config.UI.fonts.body?.url ?? 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap' }
rel="stylesheet" rel="stylesheet"
/> />
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const AccountsLabelSearch = dynamic(() => import('ui/pages/AccountsLabelSearch'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/accounts/label/[slug]">
<AccountsLabelSearch/>
</PageNextJs>
);
};
export default Page;
export { accountsLabelSearch as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -17,6 +17,8 @@ const Batch = dynamic(() => { ...@@ -17,6 +17,8 @@ const Batch = dynamic(() => {
switch (rollupFeature.type) { switch (rollupFeature.type) {
case 'arbitrum': case 'arbitrum':
return import('ui/pages/ArbitrumL2TxnBatch'); return import('ui/pages/ArbitrumL2TxnBatch');
case 'optimistic':
return import('ui/pages/OptimisticL2TxnBatch');
case 'zkEvm': case 'zkEvm':
return import('ui/pages/ZkEvmL2TxnBatch'); return import('ui/pages/ZkEvmL2TxnBatch');
case 'zkSync': case 'zkSync':
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
| "block_slim" | "block_slim"
| "block" | "block"
| "brands/blockscout" | "brands/blockscout"
| "brands/celenium"
| "brands/safe" | "brands/safe"
| "brands/solidity_scan" | "brands/solidity_scan"
| "burger" | "burger"
......
<svg viewBox="0 0 15 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.4 12.07c-.164-.152-.302-.312-.456-.454-.703-.668-.168-1.364-.168-1.364s-.844.132-2.113-.128a4.273 4.273 0 0 0-4.44 1.175 2.618 2.618 0 0 0-2.084 2.334 4.722 4.722 0 0 0-.002.5 4.64 4.64 0 0 0 .024.209s.023.132.038.196l.025.095v.003l.028.094.014.04a2.6 2.6 0 0 1 .286-.97.38.38 0 0 1 .346-.196 7 7 0 0 1 1.352.218 4.28 4.28 0 0 1 2.343 2.9h.001a4.683 4.683 0 0 1-.152 2.937A7.22 7.22 0 0 0 14.4 12.07Zm-4.82-.62a.644.644 0 0 1-1.074-.138c.118-.184.239-.278.36-.328.473-.212.807-.22.807-.22.1.221.073.489-.094.686Z" fill="url(#a)"/>
<path d="M9.594 16.721a4.743 4.743 0 0 1 .107.5l1.551 1.474.088.021a7.247 7.247 0 0 0 2.415-2.86c-3.78-3.424-7.12-2.33-7.856-2.252.464.029.916.103 1.352.218a4.28 4.28 0 0 1 2.343 2.9Z" fill="url(#b)"/>
<path d="M7.605.209A.46.46 0 0 0 7.22 0a.459.459 0 0 0-.387.209L1.381 8.544A7.185 7.185 0 0 0 0 12.782c-.004 3.86 3.064 7.044 6.889 7.218.562-.986.571-2.159.565-2.426a4.866 4.866 0 0 1-.236.006h-.033a4.757 4.757 0 0 1-3.361-1.422 4.755 4.755 0 0 1-1.396-3.374 4.751 4.751 0 0 1 .915-2.81l3.875-5.92L9.53 7.69a6.433 6.433 0 0 1 3.095.254L7.605.209Z" fill="url(#c)"/>
<path d="M12.256 17.96c-.686-1.602-1.57-2.637-2.252-3.249-1.07-.958-1.997-1.25-2.875-1.24-.877.01-1.361.15-1.361.15a.408.408 0 0 1 .13-.017c.465.029.917.102 1.352.217a4.28 4.28 0 0 1 2.344 2.9 4.683 4.683 0 0 1-.152 2.937 7.22 7.22 0 0 0 2.814-1.7v.001Z" fill="url(#d)"/>
<defs>
<linearGradient id="a" x1="7.369" y1="12.845" x2="12.113" y2="14.707" gradientUnits="userSpaceOnUse">
<stop stop-color="#FDF0C2"/>
<stop offset="1" stop-color="#F6C789"/>
</linearGradient>
<linearGradient id="b" x1="9.827" y1="13.377" x2="9.837" y2="18.83" gradientUnits="userSpaceOnUse">
<stop stop-color="#FDF1C3"/>
<stop offset=".625" stop-color="#F5C57D"/>
</linearGradient>
<linearGradient id="c" x1="2.388" y1="15.63" x2="11.492" y2="4.616" gradientUnits="userSpaceOnUse">
<stop stop-color="#F5C060"/>
<stop offset="1" stop-color="#F0994D"/>
</linearGradient>
<linearGradient id="d" x1="9.012" y1="13.47" x2="9.009" y2="15.153" gradientUnits="userSpaceOnUse">
<stop stop-color="#F5C780"/>
<stop offset="1" stop-color="#FADFA1"/>
</linearGradient>
</defs>
</svg>
import type { import type {
OptimismL2TxnBatch,
OptimisticL2DepositsItem, OptimisticL2DepositsItem,
OptimisticL2DisputeGamesItem, OptimisticL2DisputeGamesItem,
OptimisticL2OutputRootsItem, OptimisticL2OutputRootsItem,
...@@ -30,14 +31,29 @@ export const L2_WITHDRAWAL_ITEM: OptimisticL2WithdrawalsItem = { ...@@ -30,14 +31,29 @@ export const L2_WITHDRAWAL_ITEM: OptimisticL2WithdrawalsItem = {
}; };
export const L2_TXN_BATCHES_ITEM: OptimisticL2TxnBatchesItem = { export const L2_TXN_BATCHES_ITEM: OptimisticL2TxnBatchesItem = {
internal_id: 260991,
batch_data_container: 'in_blob4844',
l1_timestamp: '2023-06-01T14:46:48.000000Z', l1_timestamp: '2023-06-01T14:46:48.000000Z',
l1_tx_hashes: [ l1_tx_hashes: [
TX_HASH, TX_HASH,
], ],
l2_block_number: 5218590, l2_block_start: 5218590,
l2_block_end: 5218777,
tx_count: 9, tx_count: 9,
}; };
export const L2_TXN_BATCH: OptimismL2TxnBatch = {
...L2_TXN_BATCHES_ITEM,
batch_data_container: 'in_blob4844',
blobs: [
{
hash: '0x01fb41e1ae9f827e13abb0ee94be2ee574a23ac31426cea630ddd18af854bc85',
l1_timestamp: '2024-09-03T13:26:23.000000Z',
l1_transaction_hash: '0xd25ee571f1701690615099b208a9431d8611d0130dc342bead6d9edc291f04b9',
},
],
};
export const L2_OUTPUT_ROOTS_ITEM: OptimisticL2OutputRootsItem = { export const L2_OUTPUT_ROOTS_ITEM: OptimisticL2OutputRootsItem = {
l1_block_number: 9103684, l1_block_number: 9103684,
l1_timestamp: '2023-06-01T15:26:12.000000Z', l1_timestamp: '2023-06-01T15:26:12.000000Z',
......
...@@ -6,12 +6,12 @@ import { TX_HASH } from './tx'; ...@@ -6,12 +6,12 @@ import { TX_HASH } from './tx';
export const PRIVATE_TAG_ADDRESS: AddressTag = { export const PRIVATE_TAG_ADDRESS: AddressTag = {
address: ADDRESS_PARAMS, address: ADDRESS_PARAMS,
address_hash: ADDRESS_HASH, address_hash: ADDRESS_HASH,
id: '4', id: 4,
name: 'placeholder', name: 'placeholder',
}; };
export const PRIVATE_TAG_TX: TransactionTag = { export const PRIVATE_TAG_TX: TransactionTag = {
id: '1', id: 1,
name: 'placeholder', name: 'placeholder',
transaction_hash: TX_HASH, transaction_hash: TX_HASH,
}; };
...@@ -21,7 +21,7 @@ export const WATCH_LIST_ITEM_WITH_TOKEN_INFO: WatchlistAddress = { ...@@ -21,7 +21,7 @@ export const WATCH_LIST_ITEM_WITH_TOKEN_INFO: WatchlistAddress = {
address_balance: '7072643779453701031672', address_balance: '7072643779453701031672',
address_hash: ADDRESS_HASH, address_hash: ADDRESS_HASH,
exchange_rate: '0.00099052', exchange_rate: '0.00099052',
id: '18', id: 18,
name: 'placeholder', name: 'placeholder',
notification_methods: { notification_methods: {
email: false, email: false,
...@@ -68,7 +68,7 @@ export const CUSTOM_ABI: CustomAbi = { ...@@ -68,7 +68,7 @@ export const CUSTOM_ABI: CustomAbi = {
], ],
contract_address: ADDRESS_PARAMS, contract_address: ADDRESS_PARAMS,
contract_address_hash: ADDRESS_HASH, contract_address_hash: ADDRESS_HASH,
id: '1', id: 1,
name: 'placeholder', name: 'placeholder',
}; };
......
...@@ -27,7 +27,7 @@ export const ADDRESS_INFO: Address = { ...@@ -27,7 +27,7 @@ export const ADDRESS_INFO: Address = {
has_tokens: false, has_tokens: false,
has_validated_blocks: false, has_validated_blocks: false,
hash: ADDRESS_HASH, hash: ADDRESS_HASH,
implementations: [ { address: ADDRESS_HASH, name: 'Proxy' } ], implementations: [ { address: ADDRESS_HASH, name: 'Transparent Upgradable Proxy' } ],
is_contract: true, is_contract: true,
is_verified: true, is_verified: true,
name: 'ChainLink Token (goerli)', name: 'ChainLink Token (goerli)',
......
...@@ -13,6 +13,8 @@ test.use({ viewport: { width: 150, height: 350 } }); ...@@ -13,6 +13,8 @@ test.use({ viewport: { width: 150, height: 350 } });
{ variant: 'ghost', withDarkMode: true, states: [ 'default', 'hovered', 'active' ] }, { variant: 'ghost', withDarkMode: true, states: [ 'default', 'hovered', 'active' ] },
{ variant: 'subtle', states: [ 'default', 'hovered' ] }, { variant: 'subtle', states: [ 'default', 'hovered' ] },
{ variant: 'subtle', colorScheme: 'gray', states: [ 'default', 'hovered' ], withDarkMode: true }, { variant: 'subtle', colorScheme: 'gray', states: [ 'default', 'hovered' ], withDarkMode: true },
{ variant: 'hero', states: [ 'default', 'hovered' ], withDarkMode: true },
{ variant: 'header', states: [ 'default', 'hovered', 'selected' ], withDarkMode: true },
].forEach(({ variant, colorScheme, withDarkMode, states }) => { ].forEach(({ variant, colorScheme, withDarkMode, states }) => {
test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => { test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => {
test('', async({ render }) => { test('', async({ render }) => {
......
...@@ -2,6 +2,8 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; ...@@ -2,6 +2,8 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools'; import { mode } from '@chakra-ui/theme-tools';
import { runIfFn } from '@chakra-ui/utils'; import { runIfFn } from '@chakra-ui/utils';
import config from 'configs/app';
const variantSolid = defineStyle((props) => { const variantSolid = defineStyle((props) => {
const { colorScheme: c } = props; const { colorScheme: c } = props;
...@@ -150,12 +152,76 @@ const variantSubtle = defineStyle((props) => { ...@@ -150,12 +152,76 @@ const variantSubtle = defineStyle((props) => {
}; };
}); });
// for buttons in the hero banner
const variantHero = defineStyle((props) => {
return {
bg: mode(
config.UI.homepage.heroBanner?.button?._default?.background?.[0] || 'blue.600',
config.UI.homepage.heroBanner?.button?._default?.background?.[1] || 'blue.600',
)(props),
color: mode(
config.UI.homepage.heroBanner?.button?._default?.text_color?.[0] || 'white',
config.UI.homepage.heroBanner?.button?._default?.text_color?.[1] || 'white',
)(props),
_hover: {
bg: mode(
config.UI.homepage.heroBanner?.button?._hover?.background?.[0] || 'blue.400',
config.UI.homepage.heroBanner?.button?._hover?.background?.[1] || 'blue.400',
)(props),
color: mode(
config.UI.homepage.heroBanner?.button?._hover?.text_color?.[0] || 'white',
config.UI.homepage.heroBanner?.button?._hover?.text_color?.[1] || 'white',
)(props),
},
'&[data-selected=true]': {
bg: mode(
config.UI.homepage.heroBanner?.button?._selected?.background?.[0] || 'blue.50',
config.UI.homepage.heroBanner?.button?._selected?.background?.[1] || 'blue.50',
)(props),
color: mode(
config.UI.homepage.heroBanner?.button?._selected?.text_color?.[0] || 'blackAlpha.800',
config.UI.homepage.heroBanner?.button?._selected?.text_color?.[1] || 'blackAlpha.800',
)(props),
},
};
});
// for buttons in the page header
const variantHeader = defineStyle((props) => {
return {
bgColor: 'transparent',
color: mode('blackAlpha.800', 'gray.400')(props),
borderColor: mode('gray.300', 'gray.600')(props),
borderWidth: props.borderWidth || '2px',
borderStyle: 'solid',
_hover: {
color: 'link_hovered',
borderColor: 'link_hovered',
},
'&[data-selected=true]': {
bgColor: mode('blackAlpha.50', 'whiteAlpha.100')(props),
color: mode('blackAlpha.800', 'whiteAlpha.800')(props),
borderColor: 'transparent',
borderWidth: props.borderWidth || '0px',
},
'&[data-selected=true][data-warning=true]': {
bgColor: mode('orange.100', 'orange.900')(props),
color: mode('blackAlpha.800', 'whiteAlpha.800')(props),
borderColor: 'transparent',
borderWidth: props.borderWidth || '0px',
},
};
});
const variants = { const variants = {
solid: variantSolid, solid: variantSolid,
outline: variantOutline, outline: variantOutline,
simple: variantSimple, simple: variantSimple,
ghost: variantGhost, ghost: variantGhost,
subtle: variantSubtle, subtle: variantSubtle,
hero: variantHero,
header: variantHeader,
}; };
const baseStyle = defineStyle({ const baseStyle = defineStyle({
......
import { theme } from '@chakra-ui/react'; import { theme } from '@chakra-ui/react';
export const BODY_TYPEFACE = 'Inter'; import config from 'configs/app';
export const HEADING_TYPEFACE = 'Poppins';
export const BODY_TYPEFACE = config.UI.fonts.body?.name ?? 'Inter';
export const HEADING_TYPEFACE = config.UI.fonts.heading?.name ?? 'Poppins';
const typography = { const typography = {
fonts: { fonts: {
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"devDependencies": { "devDependencies": {
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
"tsconfig-paths-webpack-plugin": "^4.1.0", "tsconfig-paths-webpack-plugin": "^4.1.0",
"webpack": "^5.88.2", "webpack": "^5.94.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
} }
}, },
...@@ -89,34 +89,11 @@ ...@@ -89,34 +89,11 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@types/eslint": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz",
"integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true, "dev": true
"license": "MIT"
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
...@@ -992,11 +969,10 @@ ...@@ -992,11 +969,10 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.7", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"braces": "^3.0.3", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
...@@ -1603,13 +1579,11 @@ ...@@ -1603,13 +1579,11 @@
} }
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.93.0", "version": "5.94.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1",
...@@ -1618,7 +1592,7 @@ ...@@ -1618,7 +1592,7 @@
"acorn-import-attributes": "^1.9.5", "acorn-import-attributes": "^1.9.5",
"browserslist": "^4.21.10", "browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.0", "enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1", "es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1", "eslint-scope": "5.1.1",
"events": "^3.2.0", "events": "^3.2.0",
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
}, },
"devDependencies": { "devDependencies": {
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
"webpack": "^5.88.2", "webpack": "^5.94.0",
"webpack-cli": "^5.1.4", "webpack-cli": "^5.1.4",
"tsconfig-paths-webpack-plugin": "^4.1.0" "tsconfig-paths-webpack-plugin": "^4.1.0"
}, },
......
...@@ -47,28 +47,12 @@ ...@@ -47,28 +47,12 @@
"@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14" "@jridgewell/sourcemap-codec" "^1.4.14"
"@types/eslint-scope@^3.7.3": "@types/estree@^1.0.5":
version "3.7.7"
resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz"
integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
dependencies:
"@types/eslint" "*"
"@types/estree" "*"
"@types/eslint@*":
version "9.6.0"
resolved "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz"
integrity sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*", "@types/estree@^1.0.5":
version "1.0.5" version "1.0.5"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/json-schema@*", "@types/json-schema@^7.0.8": "@types/json-schema@^7.0.8":
version "7.0.15" version "7.0.15"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
...@@ -348,7 +332,7 @@ electron-to-chromium@^1.4.820: ...@@ -348,7 +332,7 @@ electron-to-chromium@^1.4.820:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz"
integrity sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA== integrity sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==
enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.0, enhanced-resolve@^5.7.0: enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0:
version "5.17.1" version "5.17.1"
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz"
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
...@@ -552,9 +536,9 @@ merge-stream@^2.0.0: ...@@ -552,9 +536,9 @@ merge-stream@^2.0.0:
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
micromatch@^4.0.0: micromatch@^4.0.0:
version "4.0.7" version "4.0.8"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies: dependencies:
braces "^3.0.3" braces "^3.0.3"
picomatch "^2.3.1" picomatch "^2.3.1"
...@@ -887,12 +871,11 @@ webpack-sources@^3.2.3: ...@@ -887,12 +871,11 @@ webpack-sources@^3.2.3:
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.88.2: webpack@^5.94.0:
version "5.93.0" version "5.94.0"
resolved "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies: dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.5" "@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1" "@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-edit" "^1.12.1"
...@@ -901,7 +884,7 @@ webpack@^5.88.2: ...@@ -901,7 +884,7 @@ webpack@^5.88.2:
acorn-import-attributes "^1.9.5" acorn-import-attributes "^1.9.5"
browserslist "^4.21.10" browserslist "^4.21.10"
chrome-trace-event "^1.0.2" chrome-trace-event "^1.0.2"
enhanced-resolve "^5.17.0" enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1" es-module-lexer "^1.2.1"
eslint-scope "5.1.1" eslint-scope "5.1.1"
events "^3.2.0" events "^3.2.0"
......
...@@ -11,6 +11,7 @@ const PRESETS = { ...@@ -11,6 +11,7 @@ const PRESETS = {
eth_sepolia: 'https://eth-sepolia.blockscout.com', eth_sepolia: 'https://eth-sepolia.blockscout.com',
gnosis: 'https://gnosis.blockscout.com', gnosis: 'https://gnosis.blockscout.com',
optimism: 'https://optimism.blockscout.com', optimism: 'https://optimism.blockscout.com',
optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com',
optimism_sepolia: 'https://optimism-sepolia.blockscout.com', optimism_sepolia: 'https://optimism-sepolia.blockscout.com',
polygon: 'https://polygon.blockscout.com', polygon: 'https://polygon.blockscout.com',
rootstock_testnet: 'https://rootstock-testnet.blockscout.com', rootstock_testnet: 'https://rootstock-testnet.blockscout.com',
......
...@@ -3,7 +3,7 @@ export interface AddressTag { ...@@ -3,7 +3,7 @@ export interface AddressTag {
address_hash: string; address_hash: string;
address: AddressParam; address: AddressParam;
name: string; name: string;
id: string; id: number;
} }
export type AddressTags = Array<AddressTag> export type AddressTags = Array<AddressTag>
...@@ -52,7 +52,7 @@ export interface Transaction { ...@@ -52,7 +52,7 @@ export interface Transaction {
export interface TransactionTag { export interface TransactionTag {
transaction_hash: string; transaction_hash: string;
name: string; name: string;
id: string; id: number;
} }
export type TransactionTags = Array<TransactionTag> export type TransactionTags = Array<TransactionTag>
...@@ -81,7 +81,7 @@ export interface WatchlistAddress { ...@@ -81,7 +81,7 @@ export interface WatchlistAddress {
exchange_rate: string; exchange_rate: string;
notification_settings: NotificationSettings; notification_settings: NotificationSettings;
notification_methods: NotificationMethods; notification_methods: NotificationMethods;
id: string; id: number;
address: AddressParam; address: AddressParam;
tokens_count: number; tokens_count: number;
tokens_fiat_value: string; tokens_fiat_value: string;
...@@ -107,7 +107,7 @@ export type CustomAbis = Array<CustomAbi> ...@@ -107,7 +107,7 @@ export type CustomAbis = Array<CustomAbi>
export interface CustomAbi { export interface CustomAbi {
name: string; name: string;
id: string; id: number;
contract_address_hash: string; contract_address_hash: string;
contract_address: AddressParam; contract_address: AddressParam;
abi: Array<AbiItem>; abi: Array<AbiItem>;
......
...@@ -142,12 +142,6 @@ export interface AddressCoinBalanceHistoryResponse { ...@@ -142,12 +142,6 @@ export interface AddressCoinBalanceHistoryResponse {
} | null; } | null;
} }
// remove after api release
export type AddressCoinBalanceHistoryChartOld = Array<{
date: string;
value: string;
}>
export type AddressCoinBalanceHistoryChart = { export type AddressCoinBalanceHistoryChart = {
items: Array<{ items: Array<{
date: string; date: string;
......
...@@ -2,7 +2,7 @@ import type { AddressMetadataTagApi } from './addressMetadata'; ...@@ -2,7 +2,7 @@ import type { AddressMetadataTagApi } from './addressMetadata';
export interface AddressImplementation { export interface AddressImplementation {
address: string; address: string;
name: string | null; name?: string | null;
} }
export interface AddressTag { export interface AddressTag {
......
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
export type AddressesItem = AddressParam &{ tx_count: string; coin_balance: string } export type AddressesItem = AddressParam & { tx_count: string; coin_balance: string | null }
export type AddressesResponse = { export type AddressesResponse = {
items: Array<AddressesItem>; items: Array<AddressesItem>;
...@@ -11,3 +11,13 @@ export type AddressesResponse = { ...@@ -11,3 +11,13 @@ export type AddressesResponse = {
} | null; } | null;
total_supply: string; total_supply: string;
} }
export interface AddressesMetadataSearchResult {
items: Array<AddressesItem>;
next_page_params: null;
}
export interface AddressesMetadataSearchFilters {
slug: string;
tag_type: string;
}
...@@ -3,6 +3,7 @@ import type { Reward } from 'types/api/reward'; ...@@ -3,6 +3,7 @@ import type { Reward } from 'types/api/reward';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2'; import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2';
import type { OptimisticL2BatchDataContainer, OptimisticL2BlobTypeEip4844, OptimisticL2BlobTypeCelestia } from './optimisticL2';
import type { TokenInfo } from './token'; import type { TokenInfo } from './token';
import type { TokenTransfer } from './tokenTransfer'; import type { TokenTransfer } from './tokenTransfer';
import type { ZkSyncBatchesItem } from './zkSyncL2'; import type { ZkSyncBatchesItem } from './zkSyncL2';
...@@ -59,6 +60,7 @@ export interface Block { ...@@ -59,6 +60,7 @@ export interface Block {
'batch_number': number | null; 'batch_number': number | null;
}; };
arbitrum?: ArbitrumBlockData; arbitrum?: ArbitrumBlockData;
optimism?: OptimismBlockData;
// CELO FIELDS // CELO FIELDS
celo?: { celo?: {
epoch_number: number; epoch_number: number;
...@@ -78,6 +80,14 @@ type ArbitrumBlockData = { ...@@ -78,6 +80,14 @@ type ArbitrumBlockData = {
'status': ArbitrumBatchStatus; 'status': ArbitrumBatchStatus;
} }
export interface OptimismBlockData {
batch_data_container: OptimisticL2BatchDataContainer;
internal_id: number;
blobs: Array<OptimisticL2BlobTypeEip4844> | Array<OptimisticL2BlobTypeCelestia> | null;
l1_timestamp: string;
l1_tx_hashes: Array<string>;
}
export interface BlocksResponse { export interface BlocksResponse {
items: Array<Block>; items: Array<Block>;
next_page_params: { next_page_params: {
......
export interface BackendVersionConfig { export interface BackendVersionConfig {
backend_version: string; backend_version: string;
} }
export interface CsvExportConfig {
limit: number;
}
...@@ -19,6 +19,20 @@ export type SmartContractLicenseType = ...@@ -19,6 +19,20 @@ export type SmartContractLicenseType =
'gnu_agpl_v3' | 'gnu_agpl_v3' |
'bsl_1_1'; 'bsl_1_1';
export type SmartContractProxyType =
| 'eip1167'
| 'eip1967'
| 'eip1822'
| 'eip930'
| 'eip2535'
| 'master_copy'
| 'basic_implementation'
| 'basic_get_implementation'
| 'comptroller'
| 'clone_with_immutable_arguments'
| 'unknown'
| null;
export interface SmartContract { export interface SmartContract {
deployed_bytecode: string | null; deployed_bytecode: string | null;
creation_bytecode: string | null; creation_bytecode: string | null;
...@@ -53,7 +67,7 @@ export interface SmartContract { ...@@ -53,7 +67,7 @@ export interface SmartContract {
remappings?: Array<string>; remappings?: Array<string>;
}; };
verified_twin_address_hash: string | null; verified_twin_address_hash: string | null;
minimal_proxy_address_hash: string | null; proxy_type: SmartContractProxyType | null;
language: string | null; language: string | null;
license_type: SmartContractLicenseType | null; license_type: SmartContractLicenseType | null;
certified?: boolean; certified?: boolean;
......
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { Block } from './block';
import type { Transaction } from './transaction';
export type OptimisticL2DepositsItem = { export type OptimisticL2DepositsItem = {
l1_block_number: number; l1_block_number: number;
...@@ -35,21 +37,82 @@ export type OptimisticL2OutputRootsResponse = { ...@@ -35,21 +37,82 @@ export type OptimisticL2OutputRootsResponse = {
}; };
} }
export type OptimisticL2BatchDataContainer = 'in_blob4844' | 'in_celestia' | 'in_calldata';
export type OptimisticL2TxnBatchesItem = { export type OptimisticL2TxnBatchesItem = {
l1_tx_hashes: Array<string>; internal_id: number;
batch_data_container?: OptimisticL2BatchDataContainer;
l1_timestamp: string; l1_timestamp: string;
l2_block_number: number; l1_tx_hashes: Array<string>;
l2_block_start: number;
l2_block_end: number;
tx_count: number; tx_count: number;
} }
export type OptimisticL2TxnBatchesResponse = { export type OptimisticL2TxnBatchesResponse = {
items: Array<OptimisticL2TxnBatchesItem>; items: Array<OptimisticL2TxnBatchesItem>;
next_page_params: { next_page_params: {
block_number: number; id: number;
items_count: number; items_count: number;
}; };
} }
export interface OptimisticL2BlobTypeEip4844 {
hash: string;
l1_timestamp: string;
l1_transaction_hash: string;
}
export interface OptimisticL2BlobTypeCelestia {
commitment: string;
height: number;
l1_timestamp: string;
l1_transaction_hash: string;
namespace: string;
}
interface OptimismL2TxnBatchBase {
internal_id: number;
l1_timestamp: string;
l1_tx_hashes: Array<string>;
l2_block_start: number;
l2_block_end: number;
tx_count: number;
}
export interface OptimismL2TxnBatchTypeCallData extends OptimismL2TxnBatchBase {
batch_data_container: 'in_calldata';
}
export interface OptimismL2TxnBatchTypeEip4844 extends OptimismL2TxnBatchBase {
batch_data_container: 'in_blob4844';
blobs: Array<OptimisticL2BlobTypeEip4844> | null;
}
export interface OptimismL2TxnBatchTypeCelestia extends OptimismL2TxnBatchBase {
batch_data_container: 'in_celestia';
blobs: Array<OptimisticL2BlobTypeCelestia> | null;
}
export type OptimismL2TxnBatch = OptimismL2TxnBatchTypeCallData | OptimismL2TxnBatchTypeEip4844 | OptimismL2TxnBatchTypeCelestia;
export type OptimismL2BatchTxs = {
items: Array<Transaction>;
next_page_params: {
block_number: number;
index: number;
items_count: number;
} | null;
}
export type OptimismL2BatchBlocks = {
items: Array<Block>;
next_page_params: {
batch_number: number;
items_count: number;
} | null;
}
export type OptimisticL2WithdrawalsItem = { export type OptimisticL2WithdrawalsItem = {
'challenge_period_end': string | null; 'challenge_period_end': string | null;
'from': AddressParam | null; 'from': AddressParam | null;
......
...@@ -19,6 +19,7 @@ export type HomeStats = { ...@@ -19,6 +19,7 @@ export type HomeStats = {
rootstock_locked_btc?: string | null; rootstock_locked_btc?: string | null;
last_output_root_size?: string | null; last_output_root_size?: string | null;
secondary_coin_price?: string | null; secondary_coin_price?: string | null;
secondary_coin_image?: string | null;
celo?: { celo?: {
epoch_number: number; epoch_number: number;
}; };
......
export const CHAIN_INDICATOR_IDS = [ 'daily_txs', 'coin_price', 'secondary_coin_price', 'market_cap', 'tvl' ] as const; 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]; export type ChainIndicatorId = typeof CHAIN_INDICATOR_IDS[number];
export const HOME_STATS_WIDGET_IDS = [
'latest_batch',
'total_blocks',
'average_block_time',
'total_txs',
'latest_l1_state_batch',
'wallet_addresses',
'gas_tracker',
'btc_locked',
'current_epoch',
] as const;
export type HomeStatsWidgetId = typeof HOME_STATS_WIDGET_IDS[number];
export interface HeroBannerButtonState {
background?: Array<string | undefined>;
text_color?: Array<string | undefined>;
}
export interface HeroBannerConfig {
background?: Array<string | undefined>;
text_color?: Array<string | undefined>;
border?: Array<string | undefined>;
button?: {
_default?: HeroBannerButtonState;
_hover?: HeroBannerButtonState;
_selected?: HeroBannerButtonState;
};
}
export interface FontFamily {
name: string;
url: string;
}
...@@ -21,6 +21,7 @@ import AddressBalance from './details/AddressBalance'; ...@@ -21,6 +21,7 @@ import AddressBalance from './details/AddressBalance';
import AddressImplementations from './details/AddressImplementations'; import AddressImplementations from './details/AddressImplementations';
import AddressNameInfo from './details/AddressNameInfo'; import AddressNameInfo from './details/AddressNameInfo';
import AddressNetWorth from './details/AddressNetWorth'; import AddressNetWorth from './details/AddressNetWorth';
import AddressSaveOnGas from './details/AddressSaveOnGas';
import TokenSelect from './tokenSelect/TokenSelect'; import TokenSelect from './tokenSelect/TokenSelect';
import useAddressCountersQuery from './utils/useAddressCountersQuery'; import useAddressCountersQuery from './utils/useAddressCountersQuery';
import type { AddressQuery } from './utils/useAddressQuery'; import type { AddressQuery } from './utils/useAddressQuery';
...@@ -211,6 +212,12 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -211,6 +212,12 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/> />
) : ) :
0 } 0 }
{ !countersQuery.isPlaceholderData && countersQuery.data?.gas_usage_count && (
<AddressSaveOnGas
gasUsed={ countersQuery.data.gas_usage_count }
address={ data.hash }
/>
) }
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
</> </>
) } ) }
......
...@@ -20,8 +20,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -20,8 +20,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
return undefined; return undefined;
} }
const dataItems = 'items' in data ? data.items : data; return data.items.map(({ date, value }) => ({
return dataItems.map(({ date, value }) => ({
date: new Date(date), date: new Date(date),
value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(), value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(),
})); }));
...@@ -35,7 +34,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -35,7 +34,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
isLoading={ isPending } isLoading={ isPending }
h="300px" h="300px"
units={ currencyUnits.ether } units={ currencyUnits.ether }
emptyText={ data && 'days' in data && `Insufficient data for the past ${ data.days } days` } emptyText={ data?.days && `Insufficient data for the past ${ data.days } days` }
/> />
); );
}; };
......
import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box, useColorModeValue } from '@chakra-ui/react'; import { Flex, Skeleton, Button, Grid, GridItem, Alert, chakra, Box, useColorModeValue } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import type { Channel } from 'phoenix'; import type { Channel } from 'phoenix';
...@@ -24,6 +24,7 @@ import LinkExternal from 'ui/shared/links/LinkExternal'; ...@@ -24,6 +24,7 @@ import LinkExternal from 'ui/shared/links/LinkExternal';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
import ContractCodeProxyPattern from './ContractCodeProxyPattern';
import ContractSecurityAudits from './ContractSecurityAudits'; import ContractSecurityAudits from './ContractSecurityAudits';
import ContractSourceCode from './ContractSourceCode'; import ContractSourceCode from './ContractSourceCode';
...@@ -230,7 +231,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => { ...@@ -230,7 +231,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
Warning! Contract bytecode has been changed and does not match the verified one. Therefore, interaction with this smart contract may be risky. Warning! Contract bytecode has been changed and does not match the verified one. Therefore, interaction with this smart contract may be risky.
</Alert> </Alert>
) } ) }
{ !data?.is_verified && data?.verified_twin_address_hash && !data?.minimal_proxy_address_hash && ( { !data?.is_verified && data?.verified_twin_address_hash && (!data?.proxy_type || data.proxy_type === 'unknown') && (
<Alert status="warning" whiteSpace="pre-wrap" flexWrap="wrap"> <Alert status="warning" whiteSpace="pre-wrap" flexWrap="wrap">
<span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span> <span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span>
<AddressEntity <AddressEntity
...@@ -246,23 +247,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => { ...@@ -246,23 +247,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
<span> page</span> <span> page</span>
</Alert> </Alert>
) } ) }
{ data?.minimal_proxy_address_hash && ( { data?.proxy_type && <ContractCodeProxyPattern type={ data.proxy_type }/> }
<Alert status="warning" flexWrap="wrap" whiteSpace="pre-wrap">
<span>Minimal Proxy Contract for </span>
<AddressEntity
address={{ hash: data.minimal_proxy_address_hash, is_contract: true }}
truncation="constant"
fontSize="sm"
fontWeight="500"
noCopy
/>
<span>. </span>
<Box>
<Link href="https://eips.ethereum.org/EIPS/eip-1167">EIP-1167</Link>
<span> - minimal bytecode implementation that delegates all calls to a known address</span>
</Box>
</Alert>
) }
</Flex> </Flex>
{ data?.is_verified && ( { data?.is_verified && (
<Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }> <Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }>
......
import React from 'react';
import { test, expect } from 'playwright/lib';
import ContractCodeProxyPattern from './ContractCodeProxyPattern';
test('proxy type with link +@mobile', async({ render }) => {
const component = await render(<ContractCodeProxyPattern type="eip1167"/>);
await expect(component).toHaveScreenshot();
});
test('proxy type with link but without description', async({ render }) => {
const component = await render(<ContractCodeProxyPattern type="master_copy"/>);
await expect(component).toHaveScreenshot();
});
test('proxy type without link', async({ render }) => {
const component = await render(<ContractCodeProxyPattern type="basic_implementation"/>);
await expect(component).toHaveScreenshot();
});
import { Alert } from '@chakra-ui/react';
import React from 'react';
import type { SmartContractProxyType } from 'types/api/contract';
import LinkExternal from 'ui/shared/links/LinkExternal';
interface Props {
type: NonNullable<SmartContractProxyType>;
}
const PROXY_TYPES: Record<NonNullable<SmartContractProxyType>, {
name: string;
link?: string;
description?: string;
}> = {
eip1167: {
name: 'EIP-1167',
link: 'https://eips.ethereum.org/EIPS/eip-1167',
description: 'Minimal proxy',
},
eip1967: {
name: 'EIP-1967',
link: 'https://eips.ethereum.org/EIPS/eip-1967',
description: 'Proxy storage slots',
},
eip1822: {
name: 'EIP-1822',
link: 'https://eips.ethereum.org/EIPS/eip-1822',
description: 'Universal upgradeable proxy standard (UUPS)',
},
eip2535: {
name: 'EIP-2535',
link: 'https://eips.ethereum.org/EIPS/eip-2535',
description: 'Diamond proxy',
},
eip930: {
name: 'ERC-930',
link: 'https://github.com/ethereum/EIPs/issues/930',
description: 'Eternal storage',
},
clone_with_immutable_arguments: {
name: 'Clones with immutable arguments',
link: 'https://github.com/wighawag/clones-with-immutable-args',
},
master_copy: {
name: 'GnosisSafe',
link: 'https://github.com/safe-global/safe-smart-account',
},
comptroller: {
name: 'Compound protocol',
link: 'https://github.com/compound-finance/compound-protocol',
},
basic_implementation: {
name: 'public implementation getter in proxy smart-contract',
},
basic_get_implementation: {
name: 'public getImplementation getter in proxy smart-contract',
},
unknown: {
name: 'Unknown proxy pattern',
},
};
const ContractCodeProxyPattern = ({ type }: Props) => {
const proxyInfo = PROXY_TYPES[type];
if (!proxyInfo || type === 'unknown') {
return null;
}
return (
<Alert status="warning" flexWrap="wrap" whiteSpace="pre-wrap">
{ proxyInfo.link ? (
<>
This proxy smart-contract is detected via <LinkExternal href={ proxyInfo.link }>{ proxyInfo.name }</LinkExternal>
{ proxyInfo.description && ` - ${ proxyInfo.description }` }
</>
) : (
<>
This proxy smart-contract is detected via { proxyInfo.name }
{ proxyInfo.description && ` - ${ proxyInfo.description }` }
</>
) }
</Alert>
);
};
export default React.memo(ContractCodeProxyPattern);
...@@ -49,9 +49,13 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -49,9 +49,13 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
const formData = React.useMemo(() => { const formData = React.useMemo(() => {
if (typeof watchListId !== 'number') {
return;
}
return { return {
address_hash: hash, address_hash: hash,
id: String(watchListId), id: watchListId,
}; };
}, [ hash, watchListId ]); }, [ hash, watchListId ]);
...@@ -83,12 +87,14 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -83,12 +87,14 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
onSuccess={ handleAddOrDeleteSuccess } onSuccess={ handleAddOrDeleteSuccess }
data={ formData } data={ formData }
/> />
{ formData && (
<DeleteAddressModal <DeleteAddressModal
{ ...deleteModalProps } { ...deleteModalProps }
onClose={ handleDeleteModalClose } onClose={ handleDeleteModalClose }
data={ formData } data={ formData }
onSuccess={ handleAddOrDeleteSuccess } onSuccess={ handleAddOrDeleteSuccess }
/> />
) }
</> </>
); );
}; };
......
import { Image, Skeleton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import * as v from 'valibot';
import config from 'configs/app';
import LinkExternal from 'ui/shared/links/LinkExternal';
import TextSeparator from 'ui/shared/TextSeparator';
const feature = config.features.saveOnGas;
const responseSchema = v.object({
percent: v.number(),
});
const ERROR_NAME = 'Invalid response schema';
interface Props {
gasUsed: string;
address: string;
}
const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
const gasUsedNumber = Number(gasUsed);
const query = useQuery({
queryKey: [ 'gas_hawk_saving_potential', { address } ],
queryFn: async() => {
if (!feature.isEnabled) {
return;
}
const response = await fetch(feature.apiUrlTemplate.replace('<address>', address));
const data = await response.json();
return data;
},
select: (response) => {
const parsedResponse = v.safeParse(responseSchema, response);
if (!parsedResponse.success) {
throw Error('Invalid response schema');
}
return parsedResponse.output;
},
placeholderData: { percent: 42 },
enabled: feature.isEnabled && gasUsedNumber > 0,
});
const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined;
React.useEffect(() => {
if (errorMessage === ERROR_NAME) {
fetch('/node-api/monitoring/invalid-api-schema', {
method: 'POST',
body: JSON.stringify({
resource: 'gas_hawk_saving_potential',
url: feature.isEnabled ? feature.apiUrlTemplate.replace('<address>', address) : undefined,
}),
});
}
}, [ address, errorMessage ]);
if (gasUsedNumber <= 0 || !feature.isEnabled || query.isError || !query.data?.percent) {
return null;
}
const percent = Math.round(query.data.percent);
if (percent < 1) {
return null;
}
return (
<>
<TextSeparator color="divider"/>
<Skeleton isLoaded={ !query.isPlaceholderData } display="flex" alignItems="center" columnGap={ 2 }>
<Image src="/static/gas_hawk_logo.svg" w="15px" h="20px" alt="GasHawk logo"/>
<LinkExternal href="https://www.gashawk.io" fontSize="sm">
Save { percent.toLocaleString(undefined, { maximumFractionDigits: 0 }) }% with GasHawk
</LinkExternal>
</Skeleton>
</>
);
};
export default React.memo(AddressSaveOnGas);
...@@ -42,7 +42,7 @@ const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => { ...@@ -42,7 +42,7 @@ const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => {
rowGap={ 4 } rowGap={ 4 }
mt={ 2 } mt={ 2 }
> >
{ data.slice(0, 9).map((domain) => <EnsEntity key={ domain.id } name={ domain.name } protocol={ domain.protocol } noCopy/>) } { data.slice(0, 9).map((domain) => <EnsEntity key={ domain.id } domain={ domain.name } protocol={ domain.protocol } noCopy/>) }
</Grid> </Grid>
); );
}; };
...@@ -126,7 +126,7 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => { ...@@ -126,7 +126,7 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => {
<Box w="100%"> <Box w="100%">
<chakra.span color="text_secondary" fontSize="xs">Primary*</chakra.span> <chakra.span color="text_secondary" fontSize="xs">Primary*</chakra.span>
<Flex alignItems="center" fontSize="md" mt={ 2 }> <Flex alignItems="center" fontSize="md" mt={ 2 }>
<EnsEntity name={ mainDomain.name } protocol={ mainDomain.protocol } fontWeight={ 600 } noCopy/> <EnsEntity domain={ mainDomain.name } protocol={ mainDomain.protocol } fontWeight={ 600 } noCopy/>
{ mainDomain.expiry_date && { mainDomain.expiry_date &&
<chakra.span color="text_secondary" whiteSpace="pre"> (expires { dayjs(mainDomain.expiry_date).fromNow() })</chakra.span> } <chakra.span color="text_secondary" whiteSpace="pre"> (expires { dayjs(mainDomain.expiry_date).fromNow() })</chakra.span> }
</Flex> </Flex>
......
...@@ -23,11 +23,7 @@ const ERC20TokensTableItem = ({ ...@@ -23,11 +23,7 @@ const ERC20TokensTableItem = ({
return ( return (
<Tr <Tr
sx={{ role="group"
'&:hover [aria-label="Add token to wallet"]': {
opacity: 1,
},
}}
> >
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<TokenEntity <TokenEntity
...@@ -46,7 +42,7 @@ const ERC20TokensTableItem = ({ ...@@ -46,7 +42,7 @@ const ERC20TokensTableItem = ({
truncation="constant" truncation="constant"
noIcon noIcon
/> />
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0"/> <AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0" _groupHover={{ opacity: 1 }}/>
</Flex> </Flex>
</Td> </Td>
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
......
...@@ -24,7 +24,7 @@ interface Params { ...@@ -24,7 +24,7 @@ interface Params {
addressQuery: AddressQuery; addressQuery: AddressQuery;
} }
export default function useAddressQuery({ hash, addressQuery }: Params): AddressCountersQuery { export default function useAddressCountersQuery({ hash, addressQuery }: Params): AddressCountersQuery {
const enabled = Boolean(hash) && !addressQuery.isPlaceholderData; const enabled = Boolean(hash) && !addressQuery.isPlaceholderData;
const apiQuery = useApiQuery<'address_counters', { status: number }>('address_counters', { const apiQuery = useApiQuery<'address_counters', { status: number }>('address_counters', {
......
...@@ -25,7 +25,7 @@ const AddressesListItem = ({ ...@@ -25,7 +25,7 @@ const AddressesListItem = ({
isLoading, isLoading,
}: Props) => { }: Props) => {
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** config.chain.currency.decimals)); const addressBalance = BigNumber(item.coin_balance || 0).div(BigNumber(10 ** config.chain.currency.decimals));
return ( return (
<ListItemMobile rowGap={ 3 }> <ListItemMobile rowGap={ 3 }>
......
...@@ -24,7 +24,7 @@ const AddressesTableItem = ({ ...@@ -24,7 +24,7 @@ const AddressesTableItem = ({
isLoading, isLoading,
}: Props) => { }: Props) => {
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** config.chain.currency.decimals)); const addressBalance = BigNumber(item.coin_balance || 0).div(BigNumber(10 ** config.chain.currency.decimals));
const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.'); const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.');
return ( return (
......
import { HStack, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import config from 'configs/app';
import { currencyUnits } from 'lib/units';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
type Props = {
item: AddressesItem;
isLoading?: boolean;
}
const AddressesLabelSearchListItem = ({
item,
isLoading,
}: Props) => {
const addressBalance = BigNumber(item.coin_balance || 0).div(BigNumber(10 ** config.chain.currency.decimals));
return (
<ListItemMobile rowGap={ 3 }>
<AddressEntity
address={ item }
isLoading={ isLoading }
fontWeight={ 700 }
w="100%"
/>
<HStack spacing={ 3 } maxW="100%" alignItems="flex-start">
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 } flexShrink={ 0 }>{ `Balance ${ currencyUnits.ether }` }</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" minW="0" whiteSpace="pre-wrap">
<span>{ addressBalance.dp(8).toFormat() }</span>
</Skeleton>
</HStack>
<HStack spacing={ 3 }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Txn count</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
<span>{ Number(item.tx_count).toLocaleString() }</span>
</Skeleton>
</HStack>
</ListItemMobile>
);
};
export default React.memo(AddressesLabelSearchListItem);
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import { currencyUnits } from 'lib/units';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressesLabelSearchTableItem from './AddressesLabelSearchTableItem';
interface Props {
items: Array<AddressesItem>;
top: number;
isLoading?: boolean;
}
const AddressesLabelSearchTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm">
<Thead top={ top }>
<Tr>
<Th width="70%">Address</Th>
<Th width="15%" isNumeric>{ `Balance ${ currencyUnits.ether }` }</Th>
<Th width="15%" isNumeric>Txn count</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<AddressesLabelSearchTableItem
key={ item.hash + (isLoading ? index : '') }
item={ item }
isLoading={ isLoading }
/>
)) }
</Tbody>
</Table>
);
};
export default AddressesLabelSearchTable;
import { Tr, Td, Text, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import config from 'configs/app';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
type Props = {
item: AddressesItem;
isLoading?: boolean;
}
const AddressesLabelSearchTableItem = ({
item,
isLoading,
}: Props) => {
const addressBalance = BigNumber(item.coin_balance || 0).div(BigNumber(10 ** config.chain.currency.decimals));
const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.');
return (
<Tr>
<Td>
<AddressEntity
address={ item }
isLoading={ isLoading }
fontWeight={ 700 }
my="2px"
/>
</Td>
<Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" maxW="100%">
<Text lineHeight="24px" as="span">{ addressBalanceChunks[0] + (addressBalanceChunks[1] ? '.' : '') }</Text>
<Text lineHeight="24px" variant="secondary" as="span">{ addressBalanceChunks[1] }</Text>
</Skeleton>
</Td>
<Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" lineHeight="24px">
{ Number(item.tx_count).toLocaleString() }
</Skeleton>
</Td>
</Tr>
);
};
export default React.memo(AddressesLabelSearchTableItem);
...@@ -19,6 +19,7 @@ import getNetworkValidationActionText from 'lib/networks/getNetworkValidationAct ...@@ -19,6 +19,7 @@ import getNetworkValidationActionText from 'lib/networks/getNetworkValidationAct
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import OptimisticL2TxnBatchDA from 'ui/shared/batch/OptimisticL2TxnBatchDA';
import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
...@@ -212,6 +213,28 @@ const BlockDetails = ({ query }: Props) => { ...@@ -212,6 +213,28 @@ const BlockDetails = ({ query }: Props) => {
</> </>
) } ) }
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.optimism && !config.UI.views.block.hiddenFields?.batch && (
<>
<DetailsInfoItem.Label
hint="Batch number"
isLoading={ isPlaceholderData }
>
Batch
</DetailsInfoItem.Label>
<DetailsInfoItem.Value columnGap={ 3 }>
{ data.optimism.internal_id ?
<BatchEntityL2 isLoading={ isPlaceholderData } number={ data.optimism.internal_id }/> :
<Skeleton isLoaded={ !isPlaceholderData }>Pending</Skeleton> }
{ data.optimism.batch_data_container && (
<OptimisticL2TxnBatchDA
container={ data.optimism.batch_data_container }
isLoading={ isPlaceholderData }
/>
) }
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem.Label <DetailsInfoItem.Label
hint="Size of the block in bytes" hint="Size of the block in bytes"
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
......
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.
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.
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