Commit 5583bdcf authored by tom's avatar tom

Merge branch 'main' into tom2drum/sx-and-more

parents e1989197 803a3526
......@@ -39,10 +39,22 @@ export const getExternalAssetFilePath = (envName: string) => {
export const buildExternalAssetFilePath = (name: string, value: string) => {
try {
const fileName = name.replace(/^NEXT_PUBLIC_/, '').replace(/_URL$/, '').toLowerCase();
const url = new URL(value);
const fileExtension = url.pathname.match(regexp.FILE_EXTENSION)?.[1];
const fileExtension = getAssetFileExtension(value);
if (!fileExtension) {
throw new Error('Cannot get file path');
}
return `/assets/configs/${ fileName }.${ fileExtension }`;
} catch (error) {
return;
}
};
function getAssetFileExtension(value: string) {
try {
const url = new URL(value);
return url.pathname.match(regexp.FILE_EXTENSION)?.[1];
} catch (error) {
return parseEnvJson(value) ? 'json' : undefined;
}
}
......@@ -49,10 +49,14 @@ get_target_filename() {
# Extract the extension from the filename
local extension="${filename##*.}"
else
# Remove query parameters from the URL and get the filename
local filename=$(basename "${url%%\?*}")
# Extract the extension from the filename
local extension="${filename##*.}"
if [[ "$url" == http* ]]; then
# Remove query parameters from the URL and get the filename
local filename=$(basename "${url%%\?*}")
# Extract the extension from the filename
local extension="${filename##*.}"
else
local extension="json"
fi
fi
# Convert the extension to lowercase
......@@ -80,16 +84,31 @@ download_and_save_asset() {
# Copy the local file to the destination
cp "${url#file://}" "$destination"
else
# Download the asset using curl
curl -s -o "$destination" "$url"
# Check if the value is a URL
if [[ "$url" == http* ]]; then
# Download the asset using curl
curl -s -o "$destination" "$url"
else
# Convert single-quoted JSON-like content to valid JSON
json_content=$(echo "${!env_var}" | sed "s/'/\"/g")
# Save the JSON content to a file
echo "$json_content" > "$destination"
fi
fi
if [[ "$url" == file://* ]] || [[ "$url" == http* ]]; then
local source_name=$url
else
local source_name="raw input"
fi
# Check if the download was successful
if [ $? -eq 0 ]; then
echo " [+] $env_var: Successfully saved file from $url to $destination."
echo " [+] $env_var: Successfully saved file from $source_name to $destination."
return 0
else
echo " [-] $env_var: Failed to save file from $url."
echo " [-] $env_var: Failed to save file from $source_name."
return 1
fi
}
......
......@@ -93,7 +93,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ |
| NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` | `mining` | Verification type in the network. Irrelevant for Arbitrum (verification type is always `posting`) and ZkEvm (verification type is always `sequencing`) L2s | - | `mining` | `validation` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` \| `mining` | Verification type in the network. Irrelevant for Arbitrum (verification type is always `posting`) and ZkEvm (verification type is always `sequencing`) L2s | - | `mining` | `validation` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ |
| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ |
......@@ -144,7 +144,7 @@ _Note_ Here, all values are arrays of up to two strings. The first string repres
| NEXT_PUBLIC_NETWORK_LOGO_DARK | `string` | Network logo for dark color mode; if not provided, **inverted** regular logo will be used instead | - | - | `https://placekitten.com/240/40` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_ICON | `string` | Network icon; used as a replacement for regular network logo when nav bar is collapsed; if not provided, placeholder will be shown; *Note* the icon size should be at least 60px by 60px | - | - | `https://placekitten.com/60/60` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_ICON_DARK | `string` | Network icon for dark color mode; if not provided, **inverted** regular icon will be used instead | - | - | `https://placekitten.com/60/60` | v1.0.x+ |
| NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) which contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` | v1.0.x+ |
| NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) or file content string representation. It contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` \| `[{'title':'Astar(EVM)','url':'https://astar.blockscout.com/','group':'Mainnets','icon':'https://example.com/astar.svg'}]` | v1.0.x+ |
| NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | v1.0.x+ |
| NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS | `Array<LinkId>` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | v1.16.0+ |
| NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES | `Array<string>` | List of menu item routes that should have a lightning label | - | - | `['/accounts']` | v1.31.0+ |
......@@ -167,7 +167,7 @@ _Note_ Here, all values are arrays of up to two strings. The first string repres
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_FOOTER_LINKS | `string` | URL of configuration file (`.json` format only) which contains list of link groups to be displayed in the footer. See [below](#footer-links-configuration-properties) list of available properties for particular group | - | - | `https://example.com/footer_links_config.json` | v1.1.1+ |
| NEXT_PUBLIC_FOOTER_LINKS | `string` | URL of configuration file (`.json` format only) or file content string representation. It contains list of link groups to be displayed in the footer. See [below](#footer-links-configuration-properties) list of available properties for particular group | - | - | `https://example.com/footer_links_config.json` \| `[{'title':'My chain','links':[{'text':'About','url':'https://example.com/about'},{'text':'Contacts','url':'https://example.com/contacts'}]}]` | v1.1.1+ |
The app version shown in the footer is derived from build-time ENV variables `NEXT_PUBLIC_GIT_TAG` and `NEXT_PUBLIC_GIT_COMMIT_SHA` and cannot be overwritten at run-time.
......@@ -429,7 +429,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ |
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ |
| NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | - |
| NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ |
&nbsp;
......
......@@ -30,6 +30,7 @@ export default function parseMetaPayload(meta: AddressMetadataTag['meta']): Addr
'data',
'alertBgColor',
'alertTextColor',
'alertStatus',
];
for (const stringField of stringFields) {
......
import type { AlertStatus } from '@chakra-ui/react';
export interface AddressMetadataInfo {
addresses: Record<string, {
tags: Array<AddressMetadataTag>;
......@@ -35,6 +37,7 @@ export interface AddressMetadataTagApi extends Omit<AddressMetadataTag, 'meta'>
data?: string;
alertBgColor?: string;
alertTextColor?: string;
alertStatus?: AlertStatus;
} | null;
}
......
......@@ -4,7 +4,8 @@ import React from 'react';
import config from 'configs/app';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMounted from 'lib/hooks/useIsMounted';
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
......@@ -63,8 +64,6 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
has_validated_blocks: false,
}), [ addressHash ]);
const isMounted = useIsMounted();
// error handling (except 404 codes)
if (addressQuery.isError) {
if (isCustomAppError(addressQuery.error)) {
......@@ -79,7 +78,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
const data = addressQuery.isError ? error404Data : addressQuery.data;
if (!data || !isMounted) {
if (!data) {
return null;
}
......@@ -219,10 +218,10 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.has_validated_blocks && (
<>
<DetailsInfoItem.Label
hint="Number of blocks validated by this validator"
hint={ `Number of blocks ${ getNetworkValidationActionText() } by this ${ getNetworkValidatorTitle() }` }
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Blocks validated
{ `Blocks ${ getNetworkValidationActionText() }` }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import * as metadataMock from 'mocks/metadata/address';
......@@ -7,7 +6,7 @@ import { test, expect } from 'playwright/lib';
import AddressMetadataAlert from './AddressMetadataAlert';
test('base view', async({ render }) => {
const component = await render(<Box mt={ 1 }><AddressMetadataAlert tags={ [ metadataMock.noteTag ] }/></Box>);
const component = await render(<AddressMetadataAlert tags={ [ metadataMock.noteTag ] }/>);
await expect(component).toHaveScreenshot();
});
import { Alert } from '@chakra-ui/react';
import { Alert, chakra } from '@chakra-ui/react';
import React from 'react';
import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata';
interface Props {
tags: Array<AddressMetadataTagFormatted> | undefined;
className?: string;
}
const AddressMetadataAlert = ({ tags }: Props) => {
const AddressMetadataAlert = ({ tags, className }: Props) => {
const noteTag = tags?.find(({ tagType }) => tagType === 'note');
if (!noteTag) {
return null;
......@@ -21,12 +22,12 @@ const AddressMetadataAlert = ({ tags }: Props) => {
return (
<Alert
mt="-4px"
mb={ 6 }
status="error"
className={ className }
status={ noteTag.meta?.alertStatus ?? 'error' }
bgColor={ noteTag.meta?.alertBgColor }
color={ noteTag.meta?.alertTextColor }
whiteSpace="pre-wrap"
display="inline-block"
sx={{
'& a': {
color: 'link',
......@@ -40,4 +41,4 @@ const AddressMetadataAlert = ({ tags }: Props) => {
);
};
export default React.memo(AddressMetadataAlert);
export default React.memo(chakra(AddressMetadataAlert));
import { Flex, Select } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/client/contract';
import CheckboxInput from 'ui/shared/CheckboxInput';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
config: SmartContractVerificationConfig;
}
const ContractVerificationFieldZkOptimization = ({ config }: Props) => {
const [ isEnabled, setIsEnabled ] = React.useState(false);
const { formState, control } = useFormContext<FormFields>();
const error = 'optimization_mode' in formState.errors ? formState.errors.optimization_mode : undefined;
const handleCheckboxChange = React.useCallback(() => {
setIsEnabled(prev => !prev);
}, []);
const renderCheckboxControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'is_optimization_enabled'>}) => (
<Flex flexShrink={ 0 }>
<CheckboxInput<FormFields, 'is_optimization_enabled'>
text="Optimization enabled"
field={ field }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
/>
</Flex>
), [ formState.isSubmitting, handleCheckboxChange ]);
const renderInputControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'optimization_mode'>}) => {
return (
<Select
size="xs"
{ ...field }
w="auto"
borderRadius="base"
isDisabled={ formState.isSubmitting }
placeholder="Optimization mode"
isInvalid={ Boolean(error) }
>
{ config.zk_optimization_modes?.map((value) => (
<option key={ value } value={ value }>
{ value }
</option>
)) }
</Select>
);
}, [ config.zk_optimization_modes, error, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Flex columnGap={ 5 } rowGap={ 2 } h={{ base: 'auto', lg: '32px' }} flexDir={{ base: 'column', lg: 'row' }}>
<Controller
name="is_optimization_enabled"
control={ control }
render={ renderCheckboxControl }
/>
{ isEnabled && (
<Controller
name="optimization_mode"
control={ control }
render={ renderInputControl }
/>
) }
</Flex>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldZkOptimization);
......@@ -10,7 +10,6 @@ import ContractVerificationFieldCompiler from '../fields/ContractVerificationFie
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
import ContractVerificationFieldZkCompiler from '../fields/ContractVerificationFieldZkCompiler';
import ContractVerificationFieldZkOptimization from '../fields/ContractVerificationFieldZkOptimization';
const FILE_TYPES = [ '.json' as const ];
const rollupFeature = config.features.rollup;
......@@ -27,7 +26,6 @@ const ContractVerificationStandardInput = ({ config }: { config: SmartContractVe
hint="Upload the standard input JSON file created during contract compilation."
required
/>
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && <ContractVerificationFieldZkOptimization config={ config }/> }
{ !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldAutodetectArgs/> }
</ContractVerificationMethod>
);
......
......@@ -54,8 +54,6 @@ export interface FormFieldsStandardInputZk {
autodetect_constructor_args: boolean;
constructor_args: string;
license_type: LicenseOption | null;
is_optimization_enabled: boolean;
optimization_mode: string | undefined;
}
export interface FormFieldsSourcify {
......
......@@ -250,12 +250,6 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
// zkSync fields
'zk_compiler' in _data && _data.zk_compiler && body.set('zk_compiler_version', _data.zk_compiler.value);
if ('is_optimization_enabled' in _data) {
body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled)));
if (_data.is_optimization_enabled && 'optimization_mode' in _data && _data.optimization_mode) {
body.set('optimization_runs', _data.optimization_mode);
}
}
return body;
}
......
......@@ -347,7 +347,7 @@ const AddressPageContent = () => {
isLoading={ isLoading }
/>
{ !addressMetadataQuery.isPending &&
<AddressMetadataAlert tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags }/> }
<AddressMetadataAlert tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags } mt="-4px" mb={ 6 }/> }
{ config.features.metasuites.isEnabled && <Box display="none" id="meta-suites__address" data-ready={ !isLoading }/> }
<AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/>
{ /* should stay before tabs to scroll up with pagination */ }
......
......@@ -8,8 +8,8 @@ import { test, expect, devices } from 'playwright/lib';
import Marketplace from './Marketplace';
const MARKETPLACE_CONFIG_URL = 'http://localhost/marketplace-config.json';
const MARKETPLACE_SECURITY_REPORTS_URL = 'https://marketplace-security-reports.json';
const MARKETPLACE_CONFIG_URL = 'http://localhost:4000/marketplace-config.json';
const MARKETPLACE_SECURITY_REPORTS_URL = 'https://localhost:4000/marketplace-security-reports.json';
test.beforeEach(async({ mockConfigResponse, mockEnvs, mockAssetResponse, page }) => {
await mockEnvs([
......
......@@ -17,8 +17,8 @@ const hooksConfig = {
},
};
const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.json';
const MARKETPLACE_SECURITY_REPORTS_URL = 'https://marketplace-security-reports.json';
const MARKETPLACE_CONFIG_URL = 'http://localhost:4000/marketplace-config.json';
const MARKETPLACE_SECURITY_REPORTS_URL = 'http://localhost:4000/marketplace-security-reports.json';
const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, mockAssetResponse, mockEnvs, mockRpcResponse, page }) => {
await mockEnvs([
......
......@@ -152,7 +152,7 @@ test('search by user op hash +@mobile', async({ render, mockApiResponse, mockEnv
test.describe('with apps', () => {
test('default view +@mobile', async({ render, mockApiResponse, mockConfigResponse, mockAssetResponse, mockEnvs }) => {
const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.json';
const MARKETPLACE_CONFIG_URL = 'https://localhost:4000/marketplace-config.json';
const hooksConfig = {
router: {
query: { q: 'o' },
......
......@@ -19,9 +19,22 @@ export default function useScrollToActiveTab({ activeTabIndex, tabsRefs, listRef
const activeTabRef = tabsRefs[activeTabIndex];
if (activeTabRef.current && listRef.current) {
const activeTabRect = activeTabRef.current.getBoundingClientRect();
const containerWidth = listRef.current.getBoundingClientRect().width;
const activeTabWidth = activeTabRef.current.getBoundingClientRect().width;
const left = tabsRefs.slice(0, activeTabIndex)
.map((tab) => tab.current?.getBoundingClientRect())
.filter(Boolean)
.map((rect) => rect.width)
.reduce((result, item) => result + item, 0);
const isWithinFirstPage = containerWidth > left + activeTabWidth;
if (isWithinFirstPage) {
return;
}
listRef.current.scrollTo({
left: activeTabRect.left,
left,
behavior: 'smooth',
});
}
......
......@@ -196,7 +196,7 @@ test('recent keywords suggest +@mobile', async({ render, page }, { project }) =>
});
test.describe('with apps', () => {
const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.json';
const MARKETPLACE_CONFIG_URL = 'http://localhost:4000/marketplace-config.json';
test('default view +@mobile', async({ render, page, mockApiResponse, mockConfigResponse, mockAssetResponse, mockEnvs }) => {
await mockEnvs([
......
......@@ -790,7 +790,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
{ data.arbitrum?.message_related_info && (
{ data.arbitrum?.contains_message && data.arbitrum?.message_related_info && (
<>
<DetailsInfoItem.Label
hint={ data.arbitrum.contains_message === 'incoming' ?
......
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