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:
- eth_sepolia
- eth_goerli
- optimism
- optimism_celestia
- optimism_sepolia
- polygon
- rootstock
......
......@@ -20,6 +20,7 @@ on:
- eth_sepolia
- eth_goerli
- optimism
- optimism_celestia
- optimism_sepolia
- polygon
- rootstock
......
......@@ -368,6 +368,7 @@
"eth_goerli",
"eth_sepolia",
"optimism",
"optimism_celestia",
"optimism_sepolia",
"polygon",
"rootstock_testnet",
......
......@@ -25,6 +25,7 @@ export { default as publicTagsSubmission } from './publicTagsSubmission';
export { default as restApiDocs } from './restApiDocs';
export { default as rollup } from './rollup';
export { default as safe } from './safe';
export { default as saveOnGas } from './saveOnGas';
export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml';
export { default as stats } from './stats';
......
......@@ -2,6 +2,8 @@ import type { Feature } from './types';
import type { RollupType } from 'types/client/rollup';
import { ROLLUP_TYPES } from 'types/client/rollup';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import { getEnvValue } from '../utils';
const type = (() => {
......@@ -21,7 +23,7 @@ const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: s
title,
isEnabled: true,
type,
L1BaseUrl,
L1BaseUrl: stripTrailingSlash(L1BaseUrl),
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 { 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 { ColorThemeId } from 'types/settings';
import type { FontFamily } from 'types/ui';
import { COLOR_THEMES } from 'lib/settings/colorTheme';
import * as features from './features';
import * as views from './ui/views';
import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from './utils';
......@@ -24,6 +26,22 @@ const hiddenLinks = (() => {
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 parsedValue = parseEnvJson<Array<NavigationLinkId>>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES'));
return Array.isArray(parsedValue) ? parsedValue : [];
......@@ -34,9 +52,6 @@ const defaultColorTheme = (() => {
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({
navigation: {
logo: {
......@@ -60,11 +75,13 @@ const UI = Object.freeze({
},
homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_CHARTS')) || [],
stats: homePageStats,
heroBanner: parseEnvJson<HeroBannerConfig>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG')),
// !!! DEPRECATED !!!
plate: {
background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND') || HOMEPAGE_PLATE_BACKGROUND_DEFAULT,
textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR') || 'white',
background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND'),
textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR'),
},
showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true,
},
views,
indexingAlert: {
......@@ -88,6 +105,10 @@ const UI = Object.freeze({
colorTheme: {
'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,
});
......
......@@ -36,4 +36,5 @@ NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/celo.png
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
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
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_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_VERIFICATION_TYPE=validation
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
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
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=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=
## sidebar
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=/
# ui config
## homepage
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
## sidebar
## footer
NEXT_PUBLIC_GIT_TAG=v1.0.11
......
......@@ -42,4 +42,5 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-rsk-testnet.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward','nonce']
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
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;
# Iterate through all environment variables
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
value="${!var}"
......
......@@ -20,6 +20,7 @@ async function run() {
return result;
}, {} as Record<string, string>);
printDeprecationWarning(appEnvs);
await checkPlaceholdersCongruity(appEnvs);
await validateEnvs(appEnvs);
......@@ -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';
import type { WalletType } from '../../../types/client/wallets';
import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
import { CHAIN_INDICATOR_IDS } from '../../../types/homepage';
import type { ChainIndicatorId } from '../../../types/homepage';
import { CHAIN_INDICATOR_IDS, HOME_STATS_WIDGET_IDS } 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 { COLOR_THEME_IDS } from '../../../types/settings';
import type { FontFamily } from '../../../types/ui';
import type { AddressViewId } from '../../../types/views/address';
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
......@@ -390,6 +391,34 @@ const navItemExternalSchema: yup.ObjectSchema<NavItemExternal> = yup
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
.object({
text: yup.string().required(),
......@@ -538,9 +567,30 @@ const schema = yup
.transform(replaceQuotes)
.json()
.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_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
NEXT_PUBLIC_FEATURED_NETWORKS: yup
......@@ -634,6 +684,18 @@ const schema = yup
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(),
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(),
NEXT_PUBLIC_COLOR_THEME_DEFAULT: yup.string().oneOf(COLOR_THEME_IDS),
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(),
// 5. Features configuration
......@@ -740,6 +802,7 @@ const schema = yup
value => value === undefined,
),
}),
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED: yup.boolean(),
// 6. External services envs
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),
......
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none
NEXT_PUBLIC_API_SPEC_URL=none
NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none
\ No newline at end of file
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
NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps']
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_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false
NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED=false
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_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_UNITS=['gwei']
NEXT_PUBLIC_IS_TESTNET=true
......@@ -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_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_SAVE_ON_GAS_ENABLED=true
......@@ -33,9 +33,12 @@ CONFIG_TEMPLATE_FILE="config.template.json"
# Path to the generated config JSON file
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
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
echo "⏳ Making request to API..."
......
......@@ -4,7 +4,7 @@ imagePullSecrets:
- name: regcred
config:
network:
id: 11155111
id: "11155111"
name: Blockscout
shortname: Blockscout
currency:
......
......@@ -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_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_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 {
AddressNFTsResponse,
AddressCollectionsResponse,
AddressNFTTokensFilter,
AddressCoinBalanceHistoryChartOld,
AddressMudTables,
AddressMudTablesFilter,
AddressMudRecords,
......@@ -39,7 +38,7 @@ import type {
AddressMudRecordsSorting,
AddressMudRecord,
} 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 {
ArbitrumL2MessagesResponse,
......@@ -62,7 +61,7 @@ import type {
BlockEpochElectionRewardDetailsResponse,
} from 'types/api/block';
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 {
SmartContract,
SmartContractVerificationConfigRaw,
......@@ -86,6 +85,9 @@ import type {
OptimisticL2TxnBatchesResponse,
OptimisticL2WithdrawalsResponse,
OptimisticL2DisputeGamesResponse,
OptimismL2TxnBatch,
OptimismL2BatchTxs,
OptimismL2BatchBlocks,
} from 'types/api/optimisticL2';
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
......@@ -159,26 +161,26 @@ export const RESOURCES = {
path: '/api/account/v2/email/resend',
},
custom_abi: {
path: '/api/account/v2/user/custom_abis/:id?',
path: '/api/account/v2/user/custom_abis{/:id}',
pathParams: [ 'id' as const ],
},
watchlist: {
path: '/api/account/v2/user/watchlist/:id?',
path: '/api/account/v2/user/watchlist{/:id}',
pathParams: [ 'id' as const ],
filterFields: [ ],
},
private_tags_address: {
path: '/api/account/v2/user/tags/address/:id?',
path: '/api/account/v2/user/tags/address{/:id}',
pathParams: [ 'id' as const ],
filterFields: [ ],
},
private_tags_tx: {
path: '/api/account/v2/user/tags/transaction/:id?',
path: '/api/account/v2/user/tags/transaction{/:id}',
pathParams: [ 'id' as const ],
filterFields: [ ],
},
api_keys: {
path: '/api/account/v2/user/api_keys/:id?',
path: '/api/account/v2/user/api_keys{/:id}',
pathParams: [ 'id' as const ],
},
......@@ -208,7 +210,7 @@ export const RESOURCES = {
},
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 ],
endpoint: getFeaturePayload(config.features.addressVerification)?.api.endpoint,
basePath: getFeaturePayload(config.features.addressVerification)?.api.basePath,
......@@ -419,6 +421,10 @@ export const RESOURCES = {
path: '/api/v2/addresses/',
filterFields: [ ],
},
addresses_metadata_search: {
path: '/api/v2/proxy/metadata/addresses',
filterFields: [ 'slug' as const, 'tag_type' as const ],
},
// ADDRESS
address: {
......@@ -679,12 +685,29 @@ export const RESOURCES = {
},
optimistic_l2_txn_batches: {
path: '/api/v2/optimism/txn-batches',
path: '/api/v2/optimism/batches',
filterFields: [],
},
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: {
......@@ -894,6 +917,9 @@ export const RESOURCES = {
config_backend_version: {
path: '/api/v2/config/backend-version',
},
config_csv_export: {
path: '/api/v2/config/csv-export',
},
// CSV EXPORT
csv_export_token_holders: {
......@@ -960,7 +986,7 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }>
export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_rewards' |
'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' |
'addresses' |
'addresses' | 'addresses_metadata_search' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' |
'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' |
......@@ -968,7 +994,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' |
'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' |
'optimistic_l2_dispute_games' |
'optimistic_l2_dispute_games' | 'optimistic_l2_txn_batch_txs' | 'optimistic_l2_txn_batch_blocks' |
'mud_worlds'| 'address_mud_tables' | 'address_mud_records' |
'shibarium_deposits' | 'shibarium_withdrawals' |
'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 :
Q extends 'tx_blobs' ? TxBlobs :
Q extends 'tx_interpretation' ? TxInterpretationResponse :
Q extends 'addresses' ? AddressesResponse :
Q extends 'addresses_metadata_search' ? AddressesMetadataSearchResult :
Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters :
Q extends 'address_tabs_counters' ? AddressTabsCounters :
......@@ -1042,7 +1069,7 @@ Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
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_tokens' ? AddressTokensResponse :
Q extends 'address_nfts' ? AddressNFTsResponse :
......@@ -1073,11 +1100,14 @@ Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse :
Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse :
Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse :
Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse :
Q extends 'optimistic_l2_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_output_roots_count' ? number :
Q extends 'optimistic_l2_withdrawals_count' ? number :
Q extends 'optimistic_l2_deposits_count' ? number :
Q extends 'optimistic_l2_txn_batches_count' ? number :
Q extends 'optimistic_l2_dispute_games_count' ? number :
never;
// !!! IMPORTANT !!!
......@@ -1087,6 +1117,7 @@ never;
/* eslint-disable @typescript-eslint/indent */
export type ResourcePayloadB<Q extends ResourceName> =
Q extends 'config_backend_version' ? BackendVersionConfig :
Q extends 'config_csv_export' ? CsvExportConfig :
Q extends 'address_metadata_info' ? AddressMetadataInfo :
Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'blob' ? Blob :
......@@ -1156,6 +1187,7 @@ Q extends 'txs_with_blobs' ? TTxsWithBlobsFilters :
Q extends 'tx_token_transfers' ? TokenTransferFilters :
Q extends 'token_transfers' ? TokenTransferFilters :
Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters :
Q extends 'addresses_metadata_search' ? AddressesMetadataSearchFilters :
Q extends 'address_token_transfers' ? AddressTokenTransferFilters :
Q extends 'address_tokens' ? AddressTokensFilter :
Q extends 'address_nfts' ? AddressNFTTokensFilter :
......
......@@ -12,6 +12,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/block/countdown': 'Regular page',
'/block/countdown/[height]': 'Regular page',
'/accounts': 'Root page',
'/accounts/label/[slug]': 'Root page',
'/address/[hash]': 'Regular page',
'/verified-contracts': 'Root page',
'/contract-verification': 'Root page',
......
......@@ -16,6 +16,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/block/countdown': DEFAULT_TEMPLATE,
'/block/countdown/[height]': 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%',
'/verified-contracts': DEFAULT_TEMPLATE,
'/contract-verification': DEFAULT_TEMPLATE,
......
......@@ -12,6 +12,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/block/countdown': '%network_name% block countdown index',
'/block/countdown/[height]': '%network_name% block %height% countdown',
'/accounts': '%network_name% top accounts',
'/accounts/label/[slug]': '%network_name% addresses search by label',
'/address/[hash]': '%network_name% address details for %hash%',
'/verified-contracts': 'Verified %network_name% contracts lookup - %network_name% explorer',
'/contract-verification': '%network_name% verify contract',
......
......@@ -10,6 +10,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/block/countdown': 'Block countdown search',
'/block/countdown/[height]': 'Block countdown',
'/accounts': 'Top accounts',
'/accounts/label/[slug]': 'Addresses search by label',
'/address/[hash]': 'Address details',
'/verified-contracts': 'Verified contracts',
'/contract-verification': 'Contract verification',
......
......@@ -35,7 +35,10 @@ const wagmiConfig = (() => {
url: config.app.baseUrl,
icons: [ config.UI.navigation.icon.default ].filter(Boolean),
},
enableEmail: true,
auth: {
email: true,
socials: [],
},
ssr: true,
batch: { multicall: { wait: 100 } },
});
......
......@@ -43,7 +43,7 @@ export const verified: SmartContract = {
file_path: '',
additional_sources: [],
verified_twin_address_hash: null,
minimal_proxy_address_hash: null,
proxy_type: null,
};
export const certified: SmartContract = {
......@@ -85,7 +85,7 @@ export const withProxyAddress: SmartContract = {
...verified,
is_verified: false,
verified_twin_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8',
minimal_proxy_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8',
proxy_type: 'eip1967',
};
export const selfDestructed: SmartContract = {
......@@ -133,7 +133,7 @@ export const nonVerified: SmartContract = {
additional_sources: [],
external_libraries: null,
verified_twin_address_hash: null,
minimal_proxy_address_hash: null,
proxy_type: null,
language: 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 = {
export const withSecondaryCoin: HomeStats = {
...base,
secondary_coin_price: '3.398',
secondary_coin_image: 'http://localhost:3100/secondary_utia.jpg',
};
export const noChartData: HomeStats = {
......
......@@ -391,3 +391,8 @@ export const withRecipientNameTag = {
...withRecipientEns,
to: addressMock.withNameTag,
};
export const withRecipientContract = {
...withRecipientEns,
to: addressMock.contract,
};
......@@ -6,6 +6,7 @@ function generateCspPolicy() {
descriptors.app(),
descriptors.ad(),
descriptors.cloudFlare(),
descriptors.gasHawk(),
descriptors.googleAnalytics(),
descriptors.googleFonts(),
descriptors.googleReCaptcha(),
......
......@@ -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 {
return {
'default-src': [
......@@ -72,8 +84,9 @@ export function app(): CspDev.DirectiveDescriptor {
// https://github.com/vercel/next.js/issues/14221#issuecomment-657258278
config.app.isDev ? KEY_WORDS.UNSAFE_EVAL : '',
// hash of ColorModeScript
// hash of ColorModeScript: system + dark
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
'\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'',
],
'style-src': [
......@@ -115,6 +128,7 @@ export function app(): CspDev.DirectiveDescriptor {
'font-src': [
KEY_WORDS.DATA,
...MAIN_DOMAINS,
...(externalFontsDomains || []),
],
'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 { app } from './app';
export { cloudFlare } from './cloudFlare';
export { gasHawk } from './gasHawk';
export { googleAnalytics } from './googleAnalytics';
export { googleFonts } from './googleFonts';
export { googleReCaptcha } from './googleReCaptcha';
......
......@@ -14,6 +14,7 @@ export function walletConnect(): CspDev.DirectiveDescriptor {
'*.web3modal.com',
'*.web3modal.org',
'*.walletconnect.com',
'*.walletconnect.org',
'wss://relay.walletconnect.com',
'wss://www.walletlink.org',
],
......
......@@ -112,7 +112,7 @@ export const optimisticRollup: GetServerSideProps<Props> = async(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) => {
if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) {
return {
......@@ -204,6 +204,16 @@ export const accounts: GetServerSideProps<Props> = async(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) => {
if (!config.features.userOps.isEnabled) {
return {
......
......@@ -13,6 +13,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/account/verified-addresses">
| StaticRoute<"/account/watchlist">
| StaticRoute<"/accounts">
| DynamicRoute<"/accounts/label/[slug]", { "slug": string }>
| DynamicRoute<"/address/[hash]/contract-verification", { "hash": string }>
| DynamicRoute<"/address/[hash]", { "hash": string }>
| StaticRoute<"/api/config">
......
......@@ -12,6 +12,7 @@
"dev:preset": "./tools/scripts/dev.preset.sh",
"dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js",
"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 ./",
"start": "next start",
"start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend:local",
......@@ -49,22 +50,22 @@
"@metamask/providers": "^10.2.1",
"@monaco-editor/react": "^4.4.6",
"@next/bundle-analyzer": "14.2.3",
"@opentelemetry/auto-instrumentations-node": "^0.39.4",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.45.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0",
"@opentelemetry/resources": "^1.18.0",
"@opentelemetry/sdk-node": "^0.45.0",
"@opentelemetry/sdk-trace-node": "^1.18.0",
"@opentelemetry/semantic-conventions": "^1.18.0",
"@opentelemetry/auto-instrumentations-node": "0.43.0",
"@opentelemetry/exporter-metrics-otlp-proto": "0.49.1",
"@opentelemetry/exporter-trace-otlp-http": "0.49.1",
"@opentelemetry/resources": "1.22.0",
"@opentelemetry/sdk-node": "0.49.1",
"@opentelemetry/sdk-trace-node": "1.22.0",
"@opentelemetry/semantic-conventions": "1.22.0",
"@sentry/cli": "^2.21.2",
"@sentry/react": "7.24.0",
"@sentry/tracing": "7.24.0",
"@slise/embed-react": "^2.2.0",
"@tanstack/react-query": "^5.4.3",
"@tanstack/react-query-devtools": "^5.4.3",
"@tanstack/react-query": "5.55.4",
"@tanstack/react-query-devtools": "5.55.4",
"@types/papaparse": "^5.3.5",
"@types/react-scroll": "^1.8.4",
"@web3modal/wagmi": "4.2.1",
"@web3modal/wagmi": "5.1.7",
"airtable": "^0.12.2",
"bignumber.js": "^9.1.0",
"blo": "^1.1.1",
......@@ -86,11 +87,11 @@
"magic-bytes.js": "1.8.0",
"mixpanel-browser": "^2.47.0",
"monaco-editor": "^0.34.1",
"next": "14.2.3",
"next": "14.2.9",
"nextjs-routes": "^1.0.8",
"node-fetch": "^3.2.9",
"papaparse": "^5.3.2",
"path-to-regexp": "^6.2.1",
"path-to-regexp": "8.1.0",
"phoenix": "^1.6.15",
"pino-http": "^8.2.1",
"pino-pretty": "^9.1.1",
......@@ -109,8 +110,8 @@
"swagger-ui-react": "^5.9.0",
"use-font-face-observer": "^1.2.1",
"valibot": "0.38.0",
"viem": "2.10.9",
"wagmi": "2.9.2",
"viem": "2.21.5",
"wagmi": "2.12.10",
"xss": "^1.0.14"
},
"devDependencies": {
......
......@@ -6,6 +6,7 @@ import React from 'react';
import logRequestFromBot from 'nextjs/utils/logRequestFromBot';
import * as serverTiming from 'nextjs/utils/serverTiming';
import config from 'configs/app';
import theme from 'theme/theme';
import * as svgSprite from 'ui/shared/IconSvg';
......@@ -35,11 +36,11 @@ class MyDocument extends Document {
<Head>
{ /* FONTS */ }
<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"
/>
<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"
/>
......
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(() => {
switch (rollupFeature.type) {
case 'arbitrum':
return import('ui/pages/ArbitrumL2TxnBatch');
case 'optimistic':
return import('ui/pages/OptimisticL2TxnBatch');
case 'zkEvm':
return import('ui/pages/ZkEvmL2TxnBatch');
case 'zkSync':
......
......@@ -24,6 +24,7 @@
| "block_slim"
| "block"
| "brands/blockscout"
| "brands/celenium"
| "brands/safe"
| "brands/solidity_scan"
| "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 {
OptimismL2TxnBatch,
OptimisticL2DepositsItem,
OptimisticL2DisputeGamesItem,
OptimisticL2OutputRootsItem,
......@@ -30,14 +31,29 @@ export const L2_WITHDRAWAL_ITEM: OptimisticL2WithdrawalsItem = {
};
export const L2_TXN_BATCHES_ITEM: OptimisticL2TxnBatchesItem = {
internal_id: 260991,
batch_data_container: 'in_blob4844',
l1_timestamp: '2023-06-01T14:46:48.000000Z',
l1_tx_hashes: [
TX_HASH,
],
l2_block_number: 5218590,
l2_block_start: 5218590,
l2_block_end: 5218777,
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 = {
l1_block_number: 9103684,
l1_timestamp: '2023-06-01T15:26:12.000000Z',
......
......@@ -6,12 +6,12 @@ import { TX_HASH } from './tx';
export const PRIVATE_TAG_ADDRESS: AddressTag = {
address: ADDRESS_PARAMS,
address_hash: ADDRESS_HASH,
id: '4',
id: 4,
name: 'placeholder',
};
export const PRIVATE_TAG_TX: TransactionTag = {
id: '1',
id: 1,
name: 'placeholder',
transaction_hash: TX_HASH,
};
......@@ -21,7 +21,7 @@ export const WATCH_LIST_ITEM_WITH_TOKEN_INFO: WatchlistAddress = {
address_balance: '7072643779453701031672',
address_hash: ADDRESS_HASH,
exchange_rate: '0.00099052',
id: '18',
id: 18,
name: 'placeholder',
notification_methods: {
email: false,
......@@ -68,7 +68,7 @@ export const CUSTOM_ABI: CustomAbi = {
],
contract_address: ADDRESS_PARAMS,
contract_address_hash: ADDRESS_HASH,
id: '1',
id: 1,
name: 'placeholder',
};
......
......@@ -27,7 +27,7 @@ export const ADDRESS_INFO: Address = {
has_tokens: false,
has_validated_blocks: false,
hash: ADDRESS_HASH,
implementations: [ { address: ADDRESS_HASH, name: 'Proxy' } ],
implementations: [ { address: ADDRESS_HASH, name: 'Transparent Upgradable Proxy' } ],
is_contract: true,
is_verified: true,
name: 'ChainLink Token (goerli)',
......
......@@ -13,6 +13,8 @@ test.use({ viewport: { width: 150, height: 350 } });
{ variant: 'ghost', withDarkMode: true, states: [ 'default', 'hovered', 'active' ] },
{ variant: 'subtle', states: [ 'default', 'hovered' ] },
{ 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 }) => {
test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => {
test('', async({ render }) => {
......
......@@ -2,6 +2,8 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
import { runIfFn } from '@chakra-ui/utils';
import config from 'configs/app';
const variantSolid = defineStyle((props) => {
const { colorScheme: c } = 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 = {
solid: variantSolid,
outline: variantOutline,
simple: variantSimple,
ghost: variantGhost,
subtle: variantSubtle,
hero: variantHero,
header: variantHeader,
};
const baseStyle = defineStyle({
......
import { theme } from '@chakra-ui/react';
export const BODY_TYPEFACE = 'Inter';
export const HEADING_TYPEFACE = 'Poppins';
import config from 'configs/app';
export const BODY_TYPEFACE = config.UI.fonts.body?.name ?? 'Inter';
export const HEADING_TYPEFACE = config.UI.fonts.heading?.name ?? 'Poppins';
const typography = {
fonts: {
......
......@@ -11,7 +11,7 @@
"devDependencies": {
"ts-loader": "^9.4.4",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"webpack": "^5.88.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4"
}
},
......@@ -89,34 +89,11 @@
"@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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true,
"license": "MIT"
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
......@@ -992,11 +969,10 @@
"license": "MIT"
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
......@@ -1603,13 +1579,11 @@
}
},
"node_modules/webpack": {
"version": "5.93.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz",
"integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==",
"version": "5.94.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1",
......@@ -1618,7 +1592,7 @@
"acorn-import-attributes": "^1.9.5",
"browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.0",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
......
......@@ -8,7 +8,7 @@
},
"devDependencies": {
"ts-loader": "^9.4.4",
"webpack": "^5.88.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"tsconfig-paths-webpack-plugin": "^4.1.0"
},
......
......@@ -47,28 +47,12 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@types/eslint-scope@^3.7.3":
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":
"@types/estree@^1.0.5":
version "1.0.5"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
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"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
......@@ -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"
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"
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz"
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
......@@ -552,9 +536,9 @@ merge-stream@^2.0.0:
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
micromatch@^4.0.0:
version "4.0.7"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz"
integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies:
braces "^3.0.3"
picomatch "^2.3.1"
......@@ -887,12 +871,11 @@ webpack-sources@^3.2.3:
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.88.2:
version "5.93.0"
resolved "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz"
integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==
webpack@^5.94.0:
version "5.94.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1"
......@@ -901,7 +884,7 @@ webpack@^5.88.2:
acorn-import-attributes "^1.9.5"
browserslist "^4.21.10"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.17.0"
enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
......
......@@ -11,6 +11,7 @@ const PRESETS = {
eth_sepolia: 'https://eth-sepolia.blockscout.com',
gnosis: 'https://gnosis.blockscout.com',
optimism: 'https://optimism.blockscout.com',
optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com',
optimism_sepolia: 'https://optimism-sepolia.blockscout.com',
polygon: 'https://polygon.blockscout.com',
rootstock_testnet: 'https://rootstock-testnet.blockscout.com',
......
......@@ -3,7 +3,7 @@ export interface AddressTag {
address_hash: string;
address: AddressParam;
name: string;
id: string;
id: number;
}
export type AddressTags = Array<AddressTag>
......@@ -52,7 +52,7 @@ export interface Transaction {
export interface TransactionTag {
transaction_hash: string;
name: string;
id: string;
id: number;
}
export type TransactionTags = Array<TransactionTag>
......@@ -81,7 +81,7 @@ export interface WatchlistAddress {
exchange_rate: string;
notification_settings: NotificationSettings;
notification_methods: NotificationMethods;
id: string;
id: number;
address: AddressParam;
tokens_count: number;
tokens_fiat_value: string;
......@@ -107,7 +107,7 @@ export type CustomAbis = Array<CustomAbi>
export interface CustomAbi {
name: string;
id: string;
id: number;
contract_address_hash: string;
contract_address: AddressParam;
abi: Array<AbiItem>;
......
......@@ -142,12 +142,6 @@ export interface AddressCoinBalanceHistoryResponse {
} | null;
}
// remove after api release
export type AddressCoinBalanceHistoryChartOld = Array<{
date: string;
value: string;
}>
export type AddressCoinBalanceHistoryChart = {
items: Array<{
date: string;
......
......@@ -2,7 +2,7 @@ import type { AddressMetadataTagApi } from './addressMetadata';
export interface AddressImplementation {
address: string;
name: string | null;
name?: string | null;
}
export interface AddressTag {
......
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 = {
items: Array<AddressesItem>;
......@@ -11,3 +11,13 @@ export type AddressesResponse = {
} | null;
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';
import type { Transaction } from 'types/api/transaction';
import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2';
import type { OptimisticL2BatchDataContainer, OptimisticL2BlobTypeEip4844, OptimisticL2BlobTypeCelestia } from './optimisticL2';
import type { TokenInfo } from './token';
import type { TokenTransfer } from './tokenTransfer';
import type { ZkSyncBatchesItem } from './zkSyncL2';
......@@ -59,6 +60,7 @@ export interface Block {
'batch_number': number | null;
};
arbitrum?: ArbitrumBlockData;
optimism?: OptimismBlockData;
// CELO FIELDS
celo?: {
epoch_number: number;
......@@ -78,6 +80,14 @@ type ArbitrumBlockData = {
'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 {
items: Array<Block>;
next_page_params: {
......
export interface BackendVersionConfig {
backend_version: string;
}
export interface CsvExportConfig {
limit: number;
}
......@@ -19,6 +19,20 @@ export type SmartContractLicenseType =
'gnu_agpl_v3' |
'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 {
deployed_bytecode: string | null;
creation_bytecode: string | null;
......@@ -53,7 +67,7 @@ export interface SmartContract {
remappings?: Array<string>;
};
verified_twin_address_hash: string | null;
minimal_proxy_address_hash: string | null;
proxy_type: SmartContractProxyType | null;
language: string | null;
license_type: SmartContractLicenseType | null;
certified?: boolean;
......
import type { AddressParam } from './addressParams';
import type { Block } from './block';
import type { Transaction } from './transaction';
export type OptimisticL2DepositsItem = {
l1_block_number: number;
......@@ -35,21 +37,82 @@ export type OptimisticL2OutputRootsResponse = {
};
}
export type OptimisticL2BatchDataContainer = 'in_blob4844' | 'in_celestia' | 'in_calldata';
export type OptimisticL2TxnBatchesItem = {
l1_tx_hashes: Array<string>;
internal_id: number;
batch_data_container?: OptimisticL2BatchDataContainer;
l1_timestamp: string;
l2_block_number: number;
l1_tx_hashes: Array<string>;
l2_block_start: number;
l2_block_end: number;
tx_count: number;
}
export type OptimisticL2TxnBatchesResponse = {
items: Array<OptimisticL2TxnBatchesItem>;
next_page_params: {
block_number: number;
id: 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 = {
'challenge_period_end': string | null;
'from': AddressParam | null;
......
......@@ -19,6 +19,7 @@ export type HomeStats = {
rootstock_locked_btc?: string | null;
last_output_root_size?: string | null;
secondary_coin_price?: string | null;
secondary_coin_image?: string | null;
celo?: {
epoch_number: number;
};
......
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 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';
import AddressImplementations from './details/AddressImplementations';
import AddressNameInfo from './details/AddressNameInfo';
import AddressNetWorth from './details/AddressNetWorth';
import AddressSaveOnGas from './details/AddressSaveOnGas';
import TokenSelect from './tokenSelect/TokenSelect';
import useAddressCountersQuery from './utils/useAddressCountersQuery';
import type { AddressQuery } from './utils/useAddressQuery';
......@@ -211,6 +212,12 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
{ !countersQuery.isPlaceholderData && countersQuery.data?.gas_usage_count && (
<AddressSaveOnGas
gasUsed={ countersQuery.data.gas_usage_count }
address={ data.hash }
/>
) }
</DetailsInfoItem.Value>
</>
) }
......
......@@ -20,8 +20,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
return undefined;
}
const dataItems = 'items' in data ? data.items : data;
return dataItems.map(({ date, value }) => ({
return data.items.map(({ date, value }) => ({
date: new Date(date),
value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(),
}));
......@@ -35,7 +34,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
isLoading={ isPending }
h="300px"
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 { useQueryClient } from '@tanstack/react-query';
import type { Channel } from 'phoenix';
......@@ -24,6 +24,7 @@ import LinkExternal from 'ui/shared/links/LinkExternal';
import LinkInternal from 'ui/shared/links/LinkInternal';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
import ContractCodeProxyPattern from './ContractCodeProxyPattern';
import ContractSecurityAudits from './ContractSecurityAudits';
import ContractSourceCode from './ContractSourceCode';
......@@ -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.
</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">
<span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span>
<AddressEntity
......@@ -246,23 +247,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
<span> page</span>
</Alert>
) }
{ data?.minimal_proxy_address_hash && (
<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>
) }
{ data?.proxy_type && <ContractCodeProxyPattern type={ data.proxy_type }/> }
</Flex>
{ data?.is_verified && (
<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) => {
}, [ deleteModalProps ]);
const formData = React.useMemo(() => {
if (typeof watchListId !== 'number') {
return;
}
return {
address_hash: hash,
id: String(watchListId),
id: watchListId,
};
}, [ hash, watchListId ]);
......@@ -83,12 +87,14 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
onSuccess={ handleAddOrDeleteSuccess }
data={ formData }
/>
<DeleteAddressModal
{ ...deleteModalProps }
onClose={ handleDeleteModalClose }
data={ formData }
onSuccess={ handleAddOrDeleteSuccess }
/>
{ formData && (
<DeleteAddressModal
{ ...deleteModalProps }
onClose={ handleDeleteModalClose }
data={ formData }
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> }) => {
rowGap={ 4 }
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>
);
};
......@@ -126,7 +126,7 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => {
<Box w="100%">
<chakra.span color="text_secondary" fontSize="xs">Primary*</chakra.span>
<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 &&
<chakra.span color="text_secondary" whiteSpace="pre"> (expires { dayjs(mainDomain.expiry_date).fromNow() })</chakra.span> }
</Flex>
......
......@@ -23,11 +23,7 @@ const ERC20TokensTableItem = ({
return (
<Tr
sx={{
'&:hover [aria-label="Add token to wallet"]': {
opacity: 1,
},
}}
role="group"
>
<Td verticalAlign="middle">
<TokenEntity
......@@ -46,7 +42,7 @@ const ERC20TokensTableItem = ({
truncation="constant"
noIcon
/>
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0"/>
<AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading } opacity="0" _groupHover={{ opacity: 1 }}/>
</Flex>
</Td>
<Td isNumeric verticalAlign="middle">
......
......@@ -24,7 +24,7 @@ interface Params {
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 apiQuery = useApiQuery<'address_counters', { status: number }>('address_counters', {
......
......@@ -25,7 +25,7 @@ const AddressesListItem = ({
isLoading,
}: 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 (
<ListItemMobile rowGap={ 3 }>
......
......@@ -24,7 +24,7 @@ const AddressesTableItem = ({
isLoading,
}: 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('.');
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
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import getQueryParamString from 'lib/router/getQueryParamString';
import { currencyUnits } from 'lib/units';
import OptimisticL2TxnBatchDA from 'ui/shared/batch/OptimisticL2TxnBatchDA';
import BlockGasUsed from 'ui/shared/block/BlockGasUsed';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
......@@ -202,7 +203,7 @@ const BlockDetails = ({ query }: Props) => {
hint="Batch number"
isLoading={ isPlaceholderData }
>
Batch
Batch
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.arbitrum.batch_number ?
......@@ -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
hint="Size of the block in bytes"
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