Commit 89af39c6 authored by isstuev's avatar isstuev

filecoin addresses support

parent c716a140
......@@ -157,3 +157,12 @@ export const validator: Address = {
watchlist_address_id: 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 {
creation_tx_hash: string | null;
exchange_rate: string | null;
ens_domain_name: string | null;
filecoin?: AddressFilecoinParams;
// TODO: if we are happy with tabs-counters method, should we delete has_something fields?
has_beacon_chain_withdrawals?: boolean;
has_decompiled_code: boolean;
......@@ -268,3 +269,27 @@ export type AddressEpochRewardsItem = {
epoch_number: number;
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';
export interface AddressImplementation {
......@@ -33,6 +34,7 @@ export type AddressParamBasic = {
reputation: number | null;
tags: Array<AddressMetadataTagApi>;
} | null;
filecoin?: AddressFilecoinParams;
}
export type AddressParam = UserTags & AddressParamBasic;
......@@ -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 }) => {
......@@ -92,3 +103,15 @@ test('validator', async({ render, mockApiResponse, page }) => {
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';
import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
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 FilecoinActorTag from './filecoin/FilecoinActorTag';
import TokenSelect from './tokenSelect/TokenSelect';
import useAddressCountersQuery from './utils/useAddressCountersQuery';
import type { AddressQuery } from './utils/useAddressQuery';
......@@ -63,6 +66,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
has_tokens: true,
has_token_transfers: true,
has_validated_blocks: false,
filecoin: undefined,
}), [ addressHash ]);
// error handling (except 404 codes)
......@@ -91,6 +95,49 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
rowGap={{ base: 1, lg: 3 }}
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 }/>
{ 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 = () => {
/>
) }
<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 }
fontFamily="heading"
fontSize="lg"
......@@ -361,7 +368,7 @@ const AddressPageContent = () => {
{ !isLoading && !addressQuery.data?.is_contract && config.features.account.isEnabled && (
<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 }/>
<HStack ml="auto" gap={ 2 }/>
{ !isLoading && addressQuery.data?.is_contract && addressQuery.data?.is_verified && config.UI.views.address.solidityscanEnabled &&
......
......@@ -77,7 +77,7 @@ const Icon = (props: IconProps) => {
<Flex marginRight={ styles.marginRight }>
<AddressIdenticon
size={ props.size === 'lg' ? 30 : 20 }
hash={ props.address.hash }
hash={ props.address.filecoin?.robust ?? props.address.hash }
/>
</Flex>
);
......@@ -115,7 +115,7 @@ const Content = chakra((props: ContentProps) => {
return (
<EntityBase.Content
{ ...props }
text={ props.address.hash }
text={ props.address.filecoin?.robust ?? props.address.hash }
/>
);
});
......@@ -126,7 +126,7 @@ const Copy = (props: CopyProps) => {
return (
<EntityBase.Copy
{ ...props }
text={ props.address.hash }
text={ props.address.filecoin?.robust ?? props.address.hash }
/>
);
};
......
......@@ -47,9 +47,9 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => {
{ data.certified && <ContractCertifiedLabel iconSize={ 5 } boxSize={ 5 } mx={ 2 }/> }
</Flex>
<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>
<CopyToClipboard text={ data.address.hash } isLoading={ isLoading }/>
<CopyToClipboard text={ data.address.filecoin?.robust ?? data.address.hash } isLoading={ isLoading }/>
</Flex>
<Flex columnGap={ 3 }>
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Balance { currencyUnits.ether }</Skeleton>
......
......@@ -46,9 +46,9 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
</Flex>
<Flex alignItems="center" ml={ 7 }>
<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>
<CopyToClipboard text={ data.address.hash } isLoading={ isLoading }/>
<CopyToClipboard text={ data.address.filecoin?.robust ?? data.address.hash } isLoading={ isLoading }/>
</Flex>
</Td>
<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