Commit 89af39c6 authored by isstuev's avatar isstuev

filecoin addresses support

parent c716a140
...@@ -157,3 +157,12 @@ export const validator: Address = { ...@@ -157,3 +157,12 @@ export const validator: Address = {
watchlist_address_id: null, watchlist_address_id: null,
ens_domain_name: null, ens_domain_name: null,
}; };
export const filecoin = {
...validator,
filecoin: {
actor_type: 'evm' as const,
id: 'f02977693',
robust: 'f410fuiwj6a3yxajbohrl5vu6ns6o2e2jriul52lvzci',
},
};
...@@ -14,6 +14,7 @@ export interface Address extends UserTags { ...@@ -14,6 +14,7 @@ export interface Address extends UserTags {
creation_tx_hash: string | null; creation_tx_hash: string | null;
exchange_rate: string | null; exchange_rate: string | null;
ens_domain_name: string | null; ens_domain_name: string | null;
filecoin?: AddressFilecoinParams;
// 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;
...@@ -268,3 +269,27 @@ export type AddressEpochRewardsItem = { ...@@ -268,3 +269,27 @@ export type AddressEpochRewardsItem = {
epoch_number: number; epoch_number: number;
associated_account: AddressParam; associated_account: AddressParam;
} }
export type AddressFilecoinParams = {
actor_type: FilecoinActorType;
id: string;
robust: string;
}
export type FilecoinActorType =
'account' |
'cron' |
'datacap' |
'eam' |
'ethaccount' |
'evm' |
'init' |
'market' |
'miner' |
'multisig' |
'paych' |
'placeholder' |
'power' |
'reward' |
'system' |
'verifreg';
import type { AddressFilecoinParams } from './address';
import type { AddressMetadataTagApi } from './addressMetadata'; import type { AddressMetadataTagApi } from './addressMetadata';
export interface AddressImplementation { export interface AddressImplementation {
...@@ -33,6 +34,7 @@ export type AddressParamBasic = { ...@@ -33,6 +34,7 @@ export type AddressParamBasic = {
reputation: number | null; reputation: number | null;
tags: Array<AddressMetadataTagApi>; tags: Array<AddressMetadataTagApi>;
} | null; } | null;
filecoin?: AddressFilecoinParams;
} }
export type AddressParam = UserTags & AddressParamBasic; export type AddressParam = UserTags & AddressParamBasic;
...@@ -44,6 +44,17 @@ test.describe('mobile', () => { ...@@ -44,6 +44,17 @@ test.describe('mobile', () => {
}); });
}); });
test('filecoin', async({ render, mockApiResponse, page }) => {
await mockApiResponse('address', addressMock.filecoin, { pathParams: { hash: ADDRESS_HASH } });
await mockApiResponse('address_counters', countersMock.forValidator, { pathParams: { hash: ADDRESS_HASH } });
const component = await render(<AddressDetails addressQuery={{ data: addressMock.filecoin } as AddressQuery}/>, { hooksConfig });
await expect(component).toHaveScreenshot({
mask: [ page.locator(pwConfig.adsBannerSelector) ],
maskColor: pwConfig.maskColor,
});
});
}); });
test('contract', async({ render, page, mockApiResponse }) => { test('contract', async({ render, page, mockApiResponse }) => {
...@@ -92,3 +103,15 @@ test('validator', async({ render, mockApiResponse, page }) => { ...@@ -92,3 +103,15 @@ test('validator', async({ render, mockApiResponse, page }) => {
maskColor: pwConfig.maskColor, maskColor: pwConfig.maskColor,
}); });
}); });
test('filecoin', async({ render, mockApiResponse, page }) => {
await mockApiResponse('address', addressMock.filecoin, { pathParams: { hash: ADDRESS_HASH } });
await mockApiResponse('address_counters', countersMock.forValidator, { pathParams: { hash: ADDRESS_HASH } });
const component = await render(<AddressDetails addressQuery={{ data: addressMock.filecoin } as AddressQuery}/>, { hooksConfig });
await expect(component).toHaveScreenshot({
mask: [ page.locator(pwConfig.adsBannerSelector) ],
maskColor: pwConfig.maskColor,
});
});
...@@ -10,18 +10,21 @@ import getQueryParamString from 'lib/router/getQueryParamString'; ...@@ -10,18 +10,21 @@ import getQueryParamString from 'lib/router/getQueryParamString';
import AddressCounterItem from 'ui/address/details/AddressCounterItem'; import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning'; import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import AddressBalance from './details/AddressBalance'; import AddressBalance from './details/AddressBalance';
import AddressImplementations from './details/AddressImplementations'; import AddressImplementations from './details/AddressImplementations';
import AddressNameInfo from './details/AddressNameInfo'; import AddressNameInfo from './details/AddressNameInfo';
import AddressNetWorth from './details/AddressNetWorth'; import AddressNetWorth from './details/AddressNetWorth';
import AddressSaveOnGas from './details/AddressSaveOnGas'; import AddressSaveOnGas from './details/AddressSaveOnGas';
import FilecoinActorTag from './filecoin/FilecoinActorTag';
import TokenSelect from './tokenSelect/TokenSelect'; import TokenSelect from './tokenSelect/TokenSelect';
import useAddressCountersQuery from './utils/useAddressCountersQuery'; import useAddressCountersQuery from './utils/useAddressCountersQuery';
import type { AddressQuery } from './utils/useAddressQuery'; import type { AddressQuery } from './utils/useAddressQuery';
...@@ -63,6 +66,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -63,6 +66,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
has_tokens: true, has_tokens: true,
has_token_transfers: true, has_token_transfers: true,
has_validated_blocks: false, has_validated_blocks: false,
filecoin: undefined,
}), [ addressHash ]); }), [ addressHash ]);
// error handling (except 404 codes) // error handling (except 404 codes)
...@@ -91,6 +95,49 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -91,6 +95,49 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
rowGap={{ base: 1, lg: 3 }} rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden" templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
> >
{ data.filecoin?.id && (
<>
<DetailsInfoItem.Label
hint="Short identifier of an address that may change with chain state updates"
>
ID
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ data.filecoin.id }</Text>
<CopyToClipboard text={ data.filecoin.id }/>
</DetailsInfoItem.Value>
</>
) }
{ data.filecoin?.actor_type && (
<>
<DetailsInfoItem.Label
hint="Identifies the purpose and behavior of the address on the Filecoin network"
>
Actor
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<FilecoinActorTag actorType={ data.filecoin.actor_type }/>
</DetailsInfoItem.Value>
</>
) }
{ (data.filecoin?.actor_type === 'evm' || data.filecoin?.actor_type === 'ethaccount') && data?.filecoin?.robust && (
<>
<DetailsInfoItem.Label
hint="0x-style address to which the Filecoin address is assigned by the Ethereum Address Manager"
>
Ethereum Address
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<Box overflow="hidden">
<HashStringShortenDynamic hash={ data.hash }/>
</Box>
<CopyToClipboard text={ data.hash }/>
</DetailsInfoItem.Value>
</>
) }
<AddressNameInfo data={ data } isLoading={ addressQuery.isPlaceholderData }/> <AddressNameInfo data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( { data.is_contract && data.creation_tx_hash && data.creator_address_hash && (
......
import { Tag } from '@chakra-ui/react';
import React from 'react';
import type { FilecoinActorType } from 'types/api/address';
const ACTOR_TYPES: Record<FilecoinActorType, string> = {
account: 'Account',
cron: 'Scheduled Tasks',
datacap: 'Data Cap Management',
eam: 'Ethereum Address Manager',
ethaccount: 'Ethereum-Compatible Account',
evm: 'Ethereum Virtual Machine',
init: 'Initialization',
market: 'Storage Market',
miner: 'Storage Provider',
multisig: 'Multi-Signature Wallet',
paych: 'Payment Channel',
placeholder: 'Placeholder Address',
power: 'Power Management',
reward: 'Incentives and Rewards',
system: 'System Operations',
verifreg: 'Verification Registry',
};
type Props = {
actorType: FilecoinActorType;
}
const FilecoinActorTag = ({ actorType }: Props) => {
const text = ACTOR_TYPES[actorType];
if (!text) {
return null;
}
return <Tag colorScheme="gray">{ text }</Tag>;
};
export default FilecoinActorTag;
...@@ -346,7 +346,14 @@ const AddressPageContent = () => { ...@@ -346,7 +346,14 @@ const AddressPageContent = () => {
/> />
) } ) }
<AddressEntity <AddressEntity
address={{ ...addressQuery.data, hash: checkSummedHash, name: '', ens_domain_name: '', implementations: null }} address={{
...addressQuery.data,
hash: checkSummedHash,
filecoin: addressQuery.data?.filecoin,
name: '',
ens_domain_name: '',
implementations: null,
}}
isLoading={ isLoading } isLoading={ isLoading }
fontFamily="heading" fontFamily="heading"
fontSize="lg" fontSize="lg"
...@@ -361,7 +368,7 @@ const AddressPageContent = () => { ...@@ -361,7 +368,7 @@ const AddressPageContent = () => {
{ !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && ( { !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && (
<AddressFavoriteButton hash={ hash } watchListId={ addressQuery.data?.watchlist_address_id }/> <AddressFavoriteButton hash={ hash } watchListId={ addressQuery.data?.watchlist_address_id }/>
) } ) }
<AddressQrCode address={{ hash: checkSummedHash }} isLoading={ isLoading }/> <AddressQrCode address={{ hash: addressQuery.data?.filecoin?.robust ?? checkSummedHash }} isLoading={ isLoading }/>
<AccountActionsMenu isLoading={ isLoading }/> <AccountActionsMenu isLoading={ isLoading }/>
<HStack ml="auto" gap={ 2 }/> <HStack ml="auto" gap={ 2 }/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled && { !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
......
...@@ -77,7 +77,7 @@ const Icon = (props: IconProps) => { ...@@ -77,7 +77,7 @@ const Icon = (props: IconProps) => {
<Flex marginRight={ styles.marginRight }> <Flex marginRight={ styles.marginRight }>
<AddressIdenticon <AddressIdenticon
size={ props.size === 'lg' ? 30 : 20 } size={ props.size === 'lg' ? 30 : 20 }
hash={ props.address.hash } hash={ props.address.filecoin?.robust ?? props.address.hash }
/> />
</Flex> </Flex>
); );
...@@ -115,7 +115,7 @@ const Content = chakra((props: ContentProps) => { ...@@ -115,7 +115,7 @@ const Content = chakra((props: ContentProps) => {
return ( return (
<EntityBase.Content <EntityBase.Content
{ ...props } { ...props }
text={ props.address.hash } text={ props.address.filecoin?.robust ?? props.address.hash }
/> />
); );
}); });
...@@ -126,7 +126,7 @@ const Copy = (props: CopyProps) => { ...@@ -126,7 +126,7 @@ const Copy = (props: CopyProps) => {
return ( return (
<EntityBase.Copy <EntityBase.Copy
{ ...props } { ...props }
text={ props.address.hash } text={ props.address.filecoin?.robust ?? props.address.hash }
/> />
); );
}; };
......
...@@ -47,9 +47,9 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => { ...@@ -47,9 +47,9 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => {
{ data.certified && <ContractCertifiedLabel iconSize={ 5 } boxSize={ 5 } mx={ 2 }/> } { data.certified && <ContractCertifiedLabel iconSize={ 5 } boxSize={ 5 } mx={ 2 }/> }
</Flex> </Flex>
<Skeleton isLoaded={ !isLoading } color="text_secondary" ml="auto"> <Skeleton isLoaded={ !isLoading } color="text_secondary" ml="auto">
<HashStringShorten hash={ data.address.hash } isTooltipDisabled/> <HashStringShorten hash={ data.address.filecoin?.robust ?? data.address.hash } isTooltipDisabled/>
</Skeleton> </Skeleton>
<CopyToClipboard text={ data.address.hash } isLoading={ isLoading }/> <CopyToClipboard text={ data.address.filecoin?.robust ?? data.address.hash } isLoading={ isLoading }/>
</Flex> </Flex>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Balance { currencyUnits.ether }</Skeleton> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Balance { currencyUnits.ether }</Skeleton>
......
...@@ -46,9 +46,9 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => { ...@@ -46,9 +46,9 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
</Flex> </Flex>
<Flex alignItems="center" ml={ 7 }> <Flex alignItems="center" ml={ 7 }>
<Skeleton isLoaded={ !isLoading } color="text_secondary" my={ 1 }> <Skeleton isLoaded={ !isLoading } color="text_secondary" my={ 1 }>
<HashStringShorten hash={ data.address.hash } isTooltipDisabled/> <HashStringShorten hash={ data.address.filecoin?.robust ?? data.address.hash } isTooltipDisabled/>
</Skeleton> </Skeleton>
<CopyToClipboard text={ data.address.hash } isLoading={ isLoading }/> <CopyToClipboard text={ data.address.filecoin?.robust ?? data.address.hash } isLoading={ isLoading }/>
</Flex> </Flex>
</Td> </Td>
<Td isNumeric> <Td isNumeric>
......
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