Commit bb745fea authored by tom goriunov's avatar tom goriunov Committed by GitHub

Zilliqa: Consensus data on Block view and Raw Input default format for Scilla contract txn (#2375)

* ENV preset for zilliqa

* add zilliqa fields to block view

* [skip ci] fix typo

* fix accordion styles

* [skip ci] make signers required again

* change default format of Raw Input for Scilla contract to UTF-8

* add default type option to log item data
parent d667de4b
...@@ -27,8 +27,10 @@ on: ...@@ -27,8 +27,10 @@ on:
- rootstock - rootstock
- shibarium - shibarium
- stability - stability
- zkevm - zkevm
- zilliqa_prototestnet
- zksync - zksync
- zora
jobs: jobs:
make_slug: make_slug:
......
...@@ -28,7 +28,9 @@ on: ...@@ -28,7 +28,9 @@ on:
- shibarium - shibarium
- stability - stability
- zkevm - zkevm
- zilliqa_prototestnet
- zksync - zksync
- zora
jobs: jobs:
make_slug: make_slug:
......
...@@ -376,7 +376,9 @@ ...@@ -376,7 +376,9 @@
"shibarium", "shibarium",
"stability_testnet", "stability_testnet",
"zkevm", "zkevm",
"zilliqa_prototestnet",
"zksync", "zksync",
"zora",
], ],
"default": "main" "default": "main"
}, },
......
# Set of ENVs for Zilliqa EVM proto-testnet network explorer
# https://zilliqa-prototestnet.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=zilliqa_prototestnet"
# 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
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['bech32','base16']
NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=zil
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['base_fee']
# Instance ENVs
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=zilliqa-prototestnet.blockscout.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_GRAPHIQL_TRANSACTION=0x3d1ded3a7924cd3256a4b1a447c9bfb194f54b9a8ceb441edb8bb01563b516db
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(90deg, rgba(52,103, 109, 1) 0.06%, rgba(105, 181, 172, 1) 99.97%)'],'text_color':['rgba(255, 255, 255, 1)']}
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ZIL
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ZIL
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zilliqa.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zilliqa-dark.svg
NEXT_PUBLIC_NETWORK_ID=33103
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zilliqa.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zilliqa-dark.svg
NEXT_PUBLIC_NETWORK_NAME=Zilliqa EVM proto-testnet
NEXT_PUBLIC_NETWORK_RPC_URL=https://api.zq2-prototestnet.zilliqa.com
NEXT_PUBLIC_NETWORK_SHORT_NAME=Zilliqa EVM proto-testnet
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zilliqa.png
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=6Ld0iT8aAAAAAJdju0CmAwGjW7JTDvIw-Q5pwt5T
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
/* eslint-disable max-len */ /* eslint-disable max-len */
import type { RpcBlock } from 'viem'; import type { RpcBlock } from 'viem';
import type { Block, BlocksResponse } from 'types/api/block'; import type { Block, BlocksResponse, ZilliqaBlockData } from 'types/api/block';
import { ZERO_ADDRESS } from 'lib/consts'; import { ZERO_ADDRESS } from 'lib/consts';
...@@ -170,6 +170,45 @@ export const celo: Block = { ...@@ -170,6 +170,45 @@ export const celo: Block = {
}, },
}; };
export const zilliqaWithAggregateQuorumCertificate: Block = {
...base,
zilliqa: {
view: 1137735,
aggregate_quorum_certificate: {
signature: '0x82d29e8f06adc890f6574c3d0ae0c811de1db695b05ed2755ef384fe21bc44f6505b99e201f6000a65f38ff6a13e286306d0e380ef1b43a273eb9947b3f11f852e14b93c258c32b516f89696fcb1190b147364b789572ebdf85d79c4cf3cbbbb',
view: 1137735,
signers: [ 1, 2, 3, 8 ],
nested_quorum_certificates: [
{
signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca',
view: 1137732,
proposed_by_validator_index: 1,
signers: [ 3, 8 ],
},
{
signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca',
view: 1137732,
proposed_by_validator_index: 2,
signers: [ 0, 2 ],
},
],
},
quorum_certificate: {
signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca',
view: 1137732,
signers: [ 0, 2, 3, 8 ],
},
},
};
export const zilliqaWithoutAggregateQuorumCertificate: Block = {
...base,
zilliqa: {
...zilliqaWithAggregateQuorumCertificate.zilliqa,
aggregate_quorum_certificate: null,
} as ZilliqaBlockData,
};
export const withBlobTxs: Block = { export const withBlobTxs: Block = {
...base, ...base,
blob_gas_price: '21518435987', blob_gas_price: '21518435987',
......
...@@ -22,6 +22,7 @@ const PRESETS = { ...@@ -22,6 +22,7 @@ const PRESETS = {
stability_testnet: 'https://stability-testnet.blockscout.com', stability_testnet: 'https://stability-testnet.blockscout.com',
zkevm: 'https://zkevm.blockscout.com', zkevm: 'https://zkevm.blockscout.com',
zksync: 'https://zksync.blockscout.com', zksync: 'https://zksync.blockscout.com',
zilliqa_prototestnet: 'https://zilliqa-prototestnet.blockscout.com',
zora: 'https://explorer.zora.energy', zora: 'https://explorer.zora.energy',
// main === staging // main === staging
main: 'https://eth-sepolia.k8s-dev.blockscout.com', main: 'https://eth-sepolia.k8s-dev.blockscout.com',
......
...@@ -16,6 +16,7 @@ export interface Address extends UserTags { ...@@ -16,6 +16,7 @@ export interface Address extends UserTags {
exchange_rate: string | null; exchange_rate: string | null;
ens_domain_name: string | null; ens_domain_name: string | null;
filecoin?: AddressFilecoinParams; filecoin?: AddressFilecoinParams;
zilliqa?: AddressZilliqaParams;
// TODO: if we are happy with tabs-counters method, should we delete has_something fields? // TODO: if we are happy with tabs-counters method, should we delete has_something fields?
has_beacon_chain_withdrawals?: boolean; has_beacon_chain_withdrawals?: boolean;
has_decompiled_code: boolean; has_decompiled_code: boolean;
...@@ -32,6 +33,10 @@ export interface Address extends UserTags { ...@@ -32,6 +33,10 @@ export interface Address extends UserTags {
watchlist_address_id: number | null; watchlist_address_id: number | null;
} }
export interface AddressZilliqaParams {
is_scilla_contract: boolean;
}
export interface AddressCounters { export interface AddressCounters {
transactions_count: string; transactions_count: string;
token_transfers_count: string; token_transfers_count: string;
......
...@@ -67,6 +67,8 @@ export interface Block { ...@@ -67,6 +67,8 @@ export interface Block {
is_epoch_block: boolean; is_epoch_block: boolean;
base_fee?: BlockBaseFeeCelo; base_fee?: BlockBaseFeeCelo;
}; };
// ZILLIQA FIELDS
zilliqa?: ZilliqaBlockData;
} }
type ArbitrumBlockData = { type ArbitrumBlockData = {
...@@ -88,6 +90,24 @@ export interface OptimismBlockData { ...@@ -88,6 +90,24 @@ export interface OptimismBlockData {
l1_transaction_hashes: Array<string>; l1_transaction_hashes: Array<string>;
} }
export interface ZilliqaBlockData {
view: number;
quorum_certificate: ZilliqaQuorumCertificate;
aggregate_quorum_certificate: (ZilliqaQuorumCertificate & {
nested_quorum_certificates: Array<ZilliqaNestedQuorumCertificate>;
}) | null;
}
export interface ZilliqaQuorumCertificate {
view: number;
signature: string;
signers: Array<number>;
}
export interface ZilliqaNestedQuorumCertificate extends ZilliqaQuorumCertificate {
proposed_by_validator_index: number;
}
export interface BlocksResponse { export interface BlocksResponse {
items: Array<Block>; items: Array<Block>;
next_page_params: { next_page_params: {
......
...@@ -90,6 +90,10 @@ export type Transaction = { ...@@ -90,6 +90,10 @@ export type Transaction = {
zksync?: Omit<ZkSyncBatchesItem, 'number' | 'transaction_count' | 'timestamp'> & { zksync?: Omit<ZkSyncBatchesItem, 'number' | 'transaction_count' | 'timestamp'> & {
batch_number: number | null; batch_number: number | null;
}; };
// Zilliqa fields
zilliqa?: {
is_scilla: boolean;
};
// blob tx fields // blob tx fields
blob_versioned_hashes?: Array<string>; blob_versioned_hashes?: Array<string>;
blob_gas_used?: string; blob_gas_used?: string;
......
...@@ -12,6 +12,7 @@ import Pagination from 'ui/shared/pagination/Pagination'; ...@@ -12,6 +12,7 @@ import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressCsvExportLink from './AddressCsvExportLink'; import AddressCsvExportLink from './AddressCsvExportLink';
import useAddressQuery from './utils/useAddressQuery';
type Props = { type Props = {
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
...@@ -39,6 +40,8 @@ const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: ...@@ -39,6 +40,8 @@ const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }:
}, },
}); });
const addressQuery = useAddressQuery({ hash });
const actionBar = ( const actionBar = (
<ActionBar mt={ -6 } showShadow justifyContent={{ base: 'space-between', lg: 'end' }}> <ActionBar mt={ -6 } showShadow justifyContent={{ base: 'space-between', lg: 'end' }}>
<AddressCsvExportLink <AddressCsvExportLink
...@@ -54,7 +57,15 @@ const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: ...@@ -54,7 +57,15 @@ const AddressLogs = ({ scrollRef, shouldRender = true, isQueryEnabled = true }:
return null; return null;
} }
const content = data?.items ? data.items.map((item, index) => <LogItem key={ index } { ...item } type="address" isLoading={ isPlaceholderData }/>) : null; const content = data?.items ? data.items.map((item, index) => (
<LogItem
key={ index }
{ ...item }
type="address"
isLoading={ isPlaceholderData }
defaultDataType={ addressQuery.data?.zilliqa?.is_scilla_contract ? 'UTF-8' : undefined }
/>
)) : null;
return ( return (
<DataListDisplay <DataListDisplay
......
...@@ -41,6 +41,7 @@ import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchH ...@@ -41,6 +41,7 @@ import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchH
import BlockDetailsBaseFeeCelo from './details/BlockDetailsBaseFeeCelo'; import BlockDetailsBaseFeeCelo from './details/BlockDetailsBaseFeeCelo';
import BlockDetailsBlobInfo from './details/BlockDetailsBlobInfo'; import BlockDetailsBlobInfo from './details/BlockDetailsBlobInfo';
import BlockDetailsZilliqaQuorumCertificate from './details/BlockDetailsZilliqaQuorumCertificate';
import type { BlockQuery } from './useBlockQuery'; import type { BlockQuery } from './useBlockQuery';
interface Props { interface Props {
...@@ -416,6 +417,22 @@ const BlockDetails = ({ query }: Props) => { ...@@ -416,6 +417,22 @@ const BlockDetails = ({ query }: Props) => {
)) ))
} }
{ typeof data.zilliqa?.view === 'number' && (
<>
<DetailsInfoItem.Label
hint="The iteration of the consensus round in which the block was proposed"
isLoading={ isPlaceholderData }
>
View
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.zilliqa.view }
</Skeleton>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/> <DetailsInfoItemDivider/>
{ data.celo?.base_fee && <BlockDetailsBaseFeeCelo data={ data.celo.base_fee }/> } { data.celo?.base_fee && <BlockDetailsBaseFeeCelo data={ data.celo.base_fee }/> }
...@@ -741,6 +758,19 @@ const BlockDetails = ({ query }: Props) => { ...@@ -741,6 +758,19 @@ const BlockDetails = ({ query }: Props) => {
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
</> </>
) } ) }
{ data.zilliqa && (
<>
<DetailsInfoItemDivider/>
<BlockDetailsZilliqaQuorumCertificate data={ data.zilliqa?.quorum_certificate }/>
{ data.zilliqa?.aggregate_quorum_certificate && (
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 2 }}/>
<BlockDetailsZilliqaQuorumCertificate data={ data.zilliqa?.aggregate_quorum_certificate }/>
</>
) }
</>
) }
</> </>
) } ) }
</Grid> </Grid>
......
import { Grid } from '@chakra-ui/react';
import React from 'react';
import type { ZilliqaQuorumCertificate } from 'types/api/block';
import * as blockMock from 'mocks/blocks/block';
import { test, expect } from 'playwright/lib';
import BlockDetailsZilliqaQuorumCertificate from './BlockDetailsZilliqaQuorumCertificate';
test('quorum certificate', async({ render }) => {
const component = await render(
<Grid
columnGap={ 8 }
rowGap={{ base: 3, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<BlockDetailsZilliqaQuorumCertificate data={ blockMock.zilliqaWithAggregateQuorumCertificate.zilliqa?.quorum_certificate as ZilliqaQuorumCertificate }/>
</Grid>,
);
await expect(component).toHaveScreenshot();
});
test('aggregated quorum certificate +@mobile', async({ render }) => {
const component = await render(
<Grid
columnGap={ 8 }
rowGap={{ base: 3, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<BlockDetailsZilliqaQuorumCertificate
data={ blockMock.zilliqaWithAggregateQuorumCertificate.zilliqa?.aggregate_quorum_certificate as ZilliqaQuorumCertificate }
/>
</Grid>,
);
await component.getByRole('button', { name: 'Nested quorum certificates' }).click();
await expect(component).toHaveScreenshot();
});
import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Divider, Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { ZilliqaNestedQuorumCertificate, ZilliqaQuorumCertificate } from 'types/api/block';
import { apos, ndash } from 'lib/html-entities';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import Hint from 'ui/shared/Hint';
function formatSigners(signers: Array<number>) {
return `[${ signers.join(', ') }]`;
}
interface Props {
data: ZilliqaQuorumCertificate & {
nested_quorum_certificates?: Array<ZilliqaNestedQuorumCertificate>;
};
}
const BlockDetailsZilliqaQuorumCertificate = ({ data }: Props) => {
const nestedBlockBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const hint = (isNested?: boolean) => (
<>
The iteration of the consensus round in which the block was proposed:<br/><br/>
View { ndash } the view number of the quorum certificate, indicating the consensus round.<br/><br/>
Signature { ndash } aggregated BLS signature representing the validators{ apos } agreement.<br/><br/>
Signers { ndash } an array of integers representing the indices of validators who participated in the quorum (indicated by the cosigned bit vector).
{ isNested && (
<>
<br/><br/>
Proposed by validator { ndash } validator index proposing the nested quorum certificate.
</>
) }
</>
);
return (
<>
<DetailsInfoItem.Label
hint={ hint() }
>
{ data.nested_quorum_certificates ? 'Aggregate quorum certificate' : 'Quorum certificate' }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value rowGap={ 0 }>
<Grid
fontSize="sm"
lineHeight={ 5 }
gridTemplateColumns="min-content 1fr"
columnGap={ 5 }
>
<GridItem fontWeight={ 600 }>View</GridItem>
<GridItem>{ data.view }</GridItem>
<DetailsInfoItemDivider my={{ base: 2, lg: 2 }} colSpan={ 2 }/>
<GridItem fontWeight={ 600 }>Signature</GridItem>
<GridItem whiteSpace="pre-wrap" wordBreak="break-word" display="flex" alignItems="flex-start" columnGap={ 5 }>
{ data.signature }
<CopyToClipboard text={ data.signature }/>
</GridItem>
<DetailsInfoItemDivider my={{ base: 2, lg: 2 }} colSpan={ 2 }/>
<GridItem fontWeight={ 600 }>Signers</GridItem>
<GridItem >{ formatSigners(data.signers) }</GridItem>
</Grid>
{ data.nested_quorum_certificates && data.nested_quorum_certificates.length > 0 && (
<>
<Divider my={ 2 }/>
<Accordion
allowToggle
w="100%"
fontSize="sm"
lineHeight={ 5 }
>
<AccordionItem borderWidth={ 0 } _last={{ borderBottomWidth: 0 }}>
{ ({ isExpanded }) => (
<>
<AccordionButton
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
display="flex"
alignItems="center"
columnGap={ 1 }
px={ 0 }
pt={ 0 }
pb={ 2 }
_hover={{ bgColor: 'inherit' }}
>
<span>Nested quorum certificates</span>
<Hint label={ hint(true) }/>
<AccordionIcon flexShrink={ 0 } boxSize={ 5 } transform={ isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)' } color="gray.500"/>
</AccordionButton>
<AccordionPanel display="flex" flexDirection="column" rowGap={ 2 } p={ 0 }>
{ data.nested_quorum_certificates?.map((item, index) => (
<Grid
key={ index }
gridTemplateColumns="90px 1fr"
columnGap={ 3 }
rowGap={ 2 }
bgColor={ nestedBlockBgColor }
p={ 4 }
borderRadius="md"
_first={{ borderTopRightRadius: 0, borderTopLeftRadius: 0 }}
>
<GridItem>View</GridItem>
<GridItem>{ item.view }</GridItem>
<GridItem>Signature</GridItem>
<GridItem whiteSpace="pre-wrap" wordBreak="break-word" display="flex" alignItems="flex-start" columnGap={ 3 }>
{ item.signature }
<CopyToClipboard text={ item.signature }/>
</GridItem>
<GridItem>Signers</GridItem>
<GridItem >{ formatSigners(item.signers) }</GridItem>
<GridItem whiteSpace="pre-wrap">Proposed by validator</GridItem>
<GridItem >{ item.proposed_by_validator_index }</GridItem>
</Grid>
)) }
</AccordionPanel>
</>
) }
</AccordionItem>
</Accordion>
</>
) }
</DetailsInfoItem.Value>
</>
);
};
export default React.memo(BlockDetailsZilliqaQuorumCertificate);
import type { ResponsiveValue } from '@chakra-ui/react';
import { GridItem, chakra } from '@chakra-ui/react'; import { GridItem, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
interface Props { interface Props {
className?: string; className?: string;
id?: string; id?: string;
colSpan?: ResponsiveValue<number | 'auto'>;
} }
const DetailsInfoItemDivider = ({ className, id }: Props) => { const DetailsInfoItemDivider = ({ className, id, colSpan }: Props) => {
return ( return (
<GridItem <GridItem
id={ id } id={ id }
className={ className } className={ className }
colSpan={{ base: undefined, lg: 2 }} colSpan={ colSpan || { base: undefined, lg: 2 } }
mt={{ base: 2, lg: 3 }} mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }} mb={{ base: 0, lg: 3 }}
borderBottom="1px solid" borderBottom="1px solid"
......
import { Select } from '@chakra-ui/react'; import { Select, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import hexToUtf8 from 'lib/hexToUtf8'; import hexToUtf8 from 'lib/hexToUtf8';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
type DataType = 'Hex' | 'UTF-8'; export type DataType = 'Hex' | 'UTF-8';
const OPTIONS: Array<DataType> = [ 'Hex', 'UTF-8' ]; const OPTIONS: Array<DataType> = [ 'Hex', 'UTF-8' ];
interface Props { interface Props {
hex: string; hex: string;
rightSlot?: React.ReactNode; rightSlot?: React.ReactNode;
defaultDataType?: DataType;
isLoading?: boolean;
minHeight?: string;
} }
const RawInputData = ({ hex, rightSlot: rightSlotProp }: Props) => { const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex', isLoading, minHeight }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('Hex'); const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>(defaultDataType);
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => { const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType); setSelectedDataType(event.target.value as DataType);
...@@ -21,9 +24,11 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp }: Props) => { ...@@ -21,9 +24,11 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp }: Props) => {
const rightSlot = ( const rightSlot = (
<> <>
<Select size="xs" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } w="auto" mr="auto"> <Skeleton isLoaded={ !isLoading } borderRadius="base" w="auto" mr="auto">
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) } <Select size="xs" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange }>
</Select> { OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
</Skeleton>
{ rightSlotProp } { rightSlotProp }
</> </>
); );
...@@ -32,8 +37,9 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp }: Props) => { ...@@ -32,8 +37,9 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp }: Props) => {
<RawDataSnippet <RawDataSnippet
data={ selectedDataType === 'Hex' ? hex : hexToUtf8(hex) } data={ selectedDataType === 'Hex' ? hex : hexToUtf8(hex) }
rightSlot={ rightSlot } rightSlot={ rightSlot }
isLoading={ isLoading }
textareaMaxHeight="220px" textareaMaxHeight="220px"
textareaMinHeight="160px" textareaMinHeight={ minHeight || '160px' }
w="100%" w="100%"
/> />
); );
......
...@@ -43,3 +43,19 @@ test('without decoded input data +@mobile', async({ render }) => { ...@@ -43,3 +43,19 @@ test('without decoded input data +@mobile', async({ render }) => {
); );
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('with default data type', async({ render }) => {
const component = await render(
<LogItem
index={ 42 }
decoded={ null }
address={ addressMocks.withoutName }
topics={ TOPICS }
data="0x6475636b"
type="address"
transaction_hash="0x404bd417203769f968aacb1d66211510db86b81303b0c68283b4eb4572e6845c"
defaultDataType="UTF-8"
/>,
);
await expect(component).toHaveScreenshot();
});
...@@ -11,10 +11,13 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity'; ...@@ -11,10 +11,13 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData'; import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
import LogTopic from 'ui/shared/logs/LogTopic'; import LogTopic from 'ui/shared/logs/LogTopic';
import type { DataType } from 'ui/shared/RawInputData';
import RawInputData from 'ui/shared/RawInputData';
type Props = Log & { type Props = Log & {
type: 'address' | 'transaction'; type: 'address' | 'transaction';
isLoading?: boolean; isLoading?: boolean;
defaultDataType?: DataType;
}; };
const RowHeader = ({ children, isLoading }: { children: React.ReactNode; isLoading?: boolean }) => ( const RowHeader = ({ children, isLoading }: { children: React.ReactNode; isLoading?: boolean }) => (
...@@ -23,7 +26,7 @@ const RowHeader = ({ children, isLoading }: { children: React.ReactNode; isLoadi ...@@ -23,7 +26,7 @@ const RowHeader = ({ children, isLoading }: { children: React.ReactNode; isLoadi
</GridItem> </GridItem>
); );
const LogItem = ({ address, index, topics, data, decoded, type, transaction_hash: txHash, isLoading }: Props) => { const LogItem = ({ address, index, topics, data, decoded, type, transaction_hash: txHash, isLoading, defaultDataType }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
...@@ -100,9 +103,13 @@ const LogItem = ({ address, index, topics, data, decoded, type, transaction_hash ...@@ -100,9 +103,13 @@ const LogItem = ({ address, index, topics, data, decoded, type, transaction_hash
)) } )) }
</GridItem> </GridItem>
<RowHeader isLoading={ isLoading }>Data</RowHeader> <RowHeader isLoading={ isLoading }>Data</RowHeader>
<Skeleton isLoaded={ !isLoading } p={ 4 } fontSize="sm" borderRadius="md" bgColor={ isLoading ? undefined : dataBgColor }> { defaultDataType ? (
{ data } <RawInputData hex={ data } isLoading={ isLoading } defaultDataType={ defaultDataType } minHeight="53px"/>
</Skeleton> ) : (
<Skeleton isLoaded={ !isLoading } p={ 4 } fontSize="sm" borderRadius="md" bgColor={ isLoading ? undefined : dataBgColor }>
{ data }
</Skeleton>
) }
</Grid> </Grid>
); );
}; };
......
...@@ -59,7 +59,15 @@ const TxLogs = ({ txQuery, logsFilter }: Props) => { ...@@ -59,7 +59,15 @@ const TxLogs = ({ txQuery, logsFilter }: Props) => {
<Pagination ml="auto" { ...pagination }/> <Pagination ml="auto" { ...pagination }/>
</ActionBar> </ActionBar>
) } ) }
{ items.map((item, index) => <LogItem key={ index } { ...item } type="transaction" isLoading={ isPlaceholderData }/>) } { items.map((item, index) => (
<LogItem
key={ index }
{ ...item }
type="transaction"
isLoading={ isPlaceholderData }
defaultDataType={ txQuery.data?.zilliqa?.is_scilla ? 'UTF-8' : undefined }
/>
)) }
</Box> </Box>
); );
}; };
......
...@@ -879,7 +879,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => { ...@@ -879,7 +879,7 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
Raw input Raw input
</DetailsInfoItem.Label> </DetailsInfoItem.Label>
<DetailsInfoItem.Value> <DetailsInfoItem.Value>
<RawInputData hex={ data.raw_input }/> <RawInputData hex={ data.raw_input } defaultDataType={ data.zilliqa?.is_scilla ? 'UTF-8' : 'Hex' }/>
</DetailsInfoItem.Value> </DetailsInfoItem.Value>
{ data.decoded_input && ( { data.decoded_input && (
......
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