mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
filecoin addresses support
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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 && (
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,39 @@
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user