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

Add support for Diamond proxy standard (#1963)

* add support for array of implementations

* refactor ContainerWithScrollY component

* contract source code: allow to choose between all implementations

* fix layout of AddressNetWorth component

* add scroll container to implementation address on write/read proxy contract tab

* update screenshots
parent 8634bc5a
......@@ -56,6 +56,7 @@ NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}]
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
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'}]
#meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true
......
......@@ -8,7 +8,7 @@ export const hash = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859';
export const withName: AddressParam = {
hash: hash,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: 'ArianeeStore',
......@@ -20,7 +20,7 @@ export const withName: AddressParam = {
export const withEns: AddressParam = {
hash: hash,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: 'ArianeeStore',
......@@ -32,7 +32,7 @@ export const withEns: AddressParam = {
export const withNameTag: AddressParam = {
hash: hash,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: 'ArianeeStore',
......@@ -50,7 +50,7 @@ export const withNameTag: AddressParam = {
export const withoutName: AddressParam = {
hash: hash,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -62,7 +62,7 @@ export const withoutName: AddressParam = {
export const token: Address = {
hash: hash,
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: false,
name: null,
......@@ -76,7 +76,6 @@ export const token: Address = {
creation_tx_hash: '0xc38cf7377bf72d6436f63c37b01b24d032101f20ec1849286dc703c712f10c98',
creator_address_hash: '0x34A9c688512ebdB575e82C50c9803F6ba2916E72',
exchange_rate: '0.04311',
implementation_address: null,
has_decompiled_code: false,
has_logs: false,
has_token_transfers: true,
......@@ -97,8 +96,9 @@ export const contract: Address = {
has_tokens: false,
has_validated_blocks: false,
hash: hash,
implementation_address: '0x2F4F4A52295940C576417d29F22EEb92B440eC89',
implementation_name: 'HomeBridge',
implementations: [
{ address: '0x2F4F4A52295940C576417d29F22EEb92B440eC89', name: 'HomeBridge' },
],
is_contract: true,
is_verified: true,
name: 'EternalStorageProxy',
......@@ -122,8 +122,7 @@ export const validator: Address = {
has_tokens: false,
has_validated_blocks: true,
hash: hash,
implementation_address: null,
implementation_name: null,
implementations: [],
is_contract: false,
is_verified: false,
name: 'Kiryl Ihnatsyeu',
......
......@@ -15,7 +15,7 @@ export const base: Block = {
height: 30146364,
miner: {
hash: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: 'Alex Emelyanov',
......@@ -65,7 +65,7 @@ export const genesis: Block = {
height: 0,
miner: {
hash: '0x0000000000000000000000000000000000000000',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -94,7 +94,7 @@ export const base2: Block = {
size: 592,
miner: {
hash: '0xDfE10D55d9248B2ED66f1647df0b0A46dEb25165',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: 'Kiryl Ihnatsyeu',
......
......@@ -3,7 +3,7 @@ import type { VerifiedContract, VerifiedContractsResponse } from 'types/api/cont
export const contract1: VerifiedContract = {
address: {
hash: '0xef490030ac0d53B70E304b6Bc5bF657dc6780bEB',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: 'MockERC20',
......@@ -26,7 +26,7 @@ export const contract1: VerifiedContract = {
export const contract2: VerifiedContract = {
address: {
hash: '0xB2218bdEbe8e90f80D04286772B0968ead666942',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: 'EternalStorageProxyWithSomeExternalLibrariesAndEvenMore',
......
......@@ -6,7 +6,7 @@ export const data: OptimisticL2WithdrawalsResponse = {
challenge_period_end: null,
from: {
hash: '0x67aab90c548b284be30b05c376001b4db90b87ba',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......
......@@ -8,7 +8,7 @@ export const data: ShibariumDepositsResponse = {
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -25,7 +25,7 @@ export const data: ShibariumDepositsResponse = {
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -42,7 +42,7 @@ export const data: ShibariumDepositsResponse = {
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......
......@@ -8,7 +8,7 @@ export const data: ShibariumWithdrawalsResponse = {
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -25,7 +25,7 @@ export const data: ShibariumWithdrawalsResponse = {
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -42,7 +42,7 @@ export const data: ShibariumWithdrawalsResponse = {
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......
......@@ -3,7 +3,7 @@ import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransf
export const erc20: TokenTransfer = {
from: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: true,
name: 'ArianeeStore',
......@@ -14,7 +14,7 @@ export const erc20: TokenTransfer = {
},
to: {
hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: false,
name: null,
......@@ -50,7 +50,7 @@ export const erc20: TokenTransfer = {
export const erc721: TokenTransfer = {
from: {
hash: '0x621C2a125ec4A6D8A7C7A655A18a2868d35eb43C',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -61,7 +61,7 @@ export const erc721: TokenTransfer = {
},
to: {
hash: '0x47eE48AEBc4ab9Ed908b805b8c8dAAa71B31Db1A',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -96,7 +96,7 @@ export const erc721: TokenTransfer = {
export const erc1155A: TokenTransfer = {
from: {
hash: '0x0000000000000000000000000000000000000000',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -107,7 +107,7 @@ export const erc1155A: TokenTransfer = {
},
to: {
hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -173,7 +173,7 @@ export const erc1155D: TokenTransfer = {
export const erc404A: TokenTransfer = {
from: {
hash: '0x0000000000000000000000000000000000000000',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -184,7 +184,7 @@ export const erc404A: TokenTransfer = {
},
to: {
hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......
......@@ -6,7 +6,7 @@ export const base: InternalTransaction = {
error: null,
from: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: true,
name: 'ArianeeStore',
......@@ -21,7 +21,7 @@ export const base: InternalTransaction = {
timestamp: '2022-10-10T14:43:05.000000Z',
to: {
hash: '0x502a9C8af2441a1E276909405119FaE21F3dC421',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: true,
name: 'ArianeeCreditHistory',
......@@ -56,7 +56,7 @@ export const withContractCreated: InternalTransaction = {
},
created_contract: {
hash: '0xdda21946FF3FAa027104b15BE6970CA756439F5a',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: 'Shavuha token',
......
......@@ -3,7 +3,7 @@ import type { TxStateChange, TxStateChanges } from 'types/api/txStateChanges';
export const mintToken: TxStateChange = {
address: {
hash: '0x0000000000000000000000000000000000000000',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -41,7 +41,7 @@ export const mintToken: TxStateChange = {
export const receiveMintedToken: TxStateChange = {
address: {
hash: '0xC8F71D0ae51AfBdB009E2eC1Ea8CC9Ee204A42B5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -79,7 +79,7 @@ export const receiveMintedToken: TxStateChange = {
export const transfer1155Token: TxStateChange = {
address: {
hash: '0x51243E83Db20F8FC2761D894067A2A9eb7B158DE',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -111,7 +111,7 @@ export const transfer1155Token: TxStateChange = {
export const receiveCoin: TxStateChange = {
address: {
hash: '0x8dC847Af872947Ac18d5d63fA646EB65d4D99560',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -131,7 +131,7 @@ export const receiveCoin: TxStateChange = {
export const sendCoin: TxStateChange = {
address: {
hash: '0xC8F71D0ae51AfBdB009E2eC1Ea8CC9Ee204A42B5',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -148,11 +148,11 @@ export const sendCoin: TxStateChange = {
type: 'coin' as const,
};
export const sendERC20Token = {
export const sendERC20Token: TxStateChange = {
address: {
hash: '0x7f6479df95Aa3036a3BE02DB6300ea201ABd9981',
ens_domain_name: null,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......@@ -175,7 +175,6 @@ export const sendERC20Token = {
symbol: 'USDT',
total_supply: '39030615894320966',
type: 'ERC-20' as const,
token_id: null,
},
type: 'token' as const,
};
......
......@@ -22,7 +22,7 @@ export const base: Transaction = {
},
from: {
hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9',
implementation_name: null,
implementations: null,
is_contract: false,
name: null,
is_verified: null,
......@@ -48,7 +48,7 @@ export const base: Transaction = {
timestamp: '2022-10-10T14:34:30.000000Z',
to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: true,
name: null,
......@@ -92,7 +92,7 @@ export const withContractCreation: Transaction = {
to: null,
created_contract: {
hash: '0xdda21946FF3FAa027104b15BE6970CA756439F5a',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: 'Shavuha token',
......@@ -111,7 +111,7 @@ export const withTokenTransfer: Transaction = {
hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196',
to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: true,
name: 'ArianeeStore',
......@@ -167,7 +167,7 @@ export const withRawRevertReason: Transaction = {
},
to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
implementations: null,
is_verified: true,
is_contract: true,
name: 'Bad guy',
......@@ -283,7 +283,7 @@ export const stabilityTx: Transaction = {
stability_fee: {
dapp_address: {
hash: '0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -308,7 +308,7 @@ export const stabilityTx: Transaction = {
total_fee: '68762500000000',
validator_address: {
hash: '0x1432997a4058acbBe562F3c1E79738c142039044',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......
......@@ -26,7 +26,7 @@ export const txInterpretation: TxInterpretationResponse = {
type: 'address',
value: {
hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......
......@@ -25,7 +25,7 @@ export const userOpData: UserOp = {
sender: {
ens_domain_name: null,
hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: null,
......@@ -34,7 +34,7 @@ export const userOpData: UserOp = {
entry_point: {
ens_domain_name: null,
hash: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: null,
......@@ -59,7 +59,7 @@ export const userOpData: UserOp = {
bundler: {
ens_domain_name: null,
hash: '0xd53Eb5203e367BbDD4f72338938224881Fc501Ab',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -95,7 +95,7 @@ export const userOpData: UserOp = {
paymaster: {
ens_domain_name: null,
hash: '0x7ceA357B5AC0639F89F9e378a1f03Aa5005C0a25',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: null,
......
......@@ -6,7 +6,7 @@ export const userOpsData: UserOpsResponse = {
address: {
ens_domain_name: null,
hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: null,
......@@ -22,7 +22,7 @@ export const userOpsData: UserOpsResponse = {
address:
{ ens_domain_name: null,
hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: null,
......@@ -38,7 +38,7 @@ export const userOpsData: UserOpsResponse = {
address: {
ens_domain_name: null,
hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2',
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: null,
name: null,
......
......@@ -9,7 +9,7 @@ export const data: WithdrawalsResponse = {
index: 11688,
receiver: {
hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -23,7 +23,7 @@ export const data: WithdrawalsResponse = {
index: 11687,
receiver: {
hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......@@ -37,7 +37,7 @@ export const data: WithdrawalsResponse = {
index: 11686,
receiver: {
hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......
......@@ -25,8 +25,7 @@ export const ADDRESS_INFO: Address = {
has_tokens: false,
has_validated_blocks: false,
hash: ADDRESS_HASH,
implementation_address: null,
implementation_name: null,
implementations: null,
is_contract: true,
is_verified: true,
name: 'ChainLink Token (goerli)',
......@@ -59,7 +58,7 @@ export const TOP_ADDRESS: AddressesItem = {
coin_balance: '11886682377162664596540805',
tx_count: '1835',
hash: '0x4f7A67464B5976d7547c860109e4432d50AfB38e',
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......
......@@ -4,7 +4,7 @@ export const ADDRESS_HASH = '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a';
export const ADDRESS_PARAMS: AddressParam = {
hash: ADDRESS_HASH,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: null,
name: null,
......
import type { Transaction } from 'types/api/transaction';
import type { UserTags } from './addressParams';
import type { UserTags, AddressImplementation } from './addressParams';
import type { Block } from './block';
import type { InternalTransaction } from './internalTransaction';
import type { NFTTokenType, TokenInfo, TokenInstance, TokenType } from './token';
......@@ -21,8 +21,7 @@ export interface Address extends UserTags {
has_tokens: boolean;
has_validated_blocks: boolean;
hash: string;
implementation_address: string | null;
implementation_name: string | null;
implementations: Array<AddressImplementation> | null;
is_contract: boolean;
is_verified: boolean;
name: string | null;
......
import type { AddressMetadataTagApi } from './addressMetadata';
export interface AddressImplementation {
address: string;
name: string | null;
}
export interface AddressTag {
label: string;
display_name: string;
......@@ -19,7 +24,9 @@ export interface UserTags {
export type AddressParamBasic = {
hash: string;
implementation_name: string | null;
// API doesn't return hash in this model yet
// will be fixed in the future releases
implementations: Array<Omit<AddressImplementation, 'address'>> | null;
name: string | null;
is_contract: boolean;
is_verified: boolean | null;
......
......@@ -17,6 +17,7 @@ import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import AddressBalance from './details/AddressBalance';
import AddressImplementations from './details/AddressImplementations';
import AddressNameInfo from './details/AddressNameInfo';
import AddressNetWorth from './details/AddressNetWorth';
import TokenSelect from './tokenSelect/TokenSelect';
......@@ -48,8 +49,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
const error404Data = React.useMemo(() => ({
hash: addressHash || '',
is_contract: false,
implementation_name: null,
implementation_address: null,
implementations: null,
token: null,
watchlist_address_id: null,
watchlist_names: null,
......@@ -112,22 +112,11 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
</DetailsInfoItem.Value>
</>
) }
{ data.is_contract && data.implementation_address && (
<>
<DetailsInfoItem.Label
hint="Implementation address of the proxy contract"
>
Implementation
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData }
noIcon
/>
</DetailsInfoItem.Value>
</>
{ data.is_contract && data.implementations && data.implementations?.length > 0 && (
<AddressImplementations
data={ data.implementations }
isLoading={ addressQuery.isPlaceholderData }
/>
) }
<AddressBalance data={ data } isLoading={ addressQuery.isPlaceholderData }/>
......@@ -152,7 +141,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
>
Net worth
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center">
<DetailsInfoItem.Value alignSelf="center" py={ 0 }>
<AddressNetWorth addressData={ addressQuery.data } addressHash={ addressHash } isLoading={ addressQuery.isPlaceholderData }/>
</DetailsInfoItem.Value>
</>
......
......@@ -27,7 +27,7 @@ test.beforeEach(async({ mockApiResponse }) => {
test('full view +@mobile +@dark-mode', async({ render, mockApiResponse, createSocket }) => {
await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.hash } });
await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.implementation_address as string } });
await mockApiResponse('contract', contractMock.withChangedByteCode, { pathParams: { hash: addressMock.contract.implementations?.[0].address as string } });
const component = await render(<ContractCode/>, { hooksConfig }, { withSocket: true });
await createSocket();
......
......@@ -224,7 +224,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
<Alert status="warning" whiteSpace="pre-wrap" flexWrap="wrap">
<span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span>
<AddressEntity
address={{ hash: data.verified_twin_address_hash, is_contract: true, implementation_name: null }}
address={{ hash: data.verified_twin_address_hash, is_contract: true }}
truncation="constant"
fontSize="sm"
fontWeight="500"
......@@ -240,7 +240,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
<Alert status="warning" flexWrap="wrap" whiteSpace="pre-wrap">
<span>Minimal Proxy Contract for </span>
<AddressEntity
address={{ hash: data.minimal_proxy_address_hash, is_contract: true, implementation_name: null }}
address={{ hash: data.minimal_proxy_address_hash, is_contract: true }}
truncation="constant"
fontSize="sm"
fontWeight="500"
......@@ -291,10 +291,10 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
isLoading={ isPlaceholderData }
/>
) }
{ data?.source_code && (
{ data?.source_code && addressHash && (
<ContractSourceCode
address={ addressHash }
implementationAddress={ addressInfo?.implementation_address ?? undefined }
implementations={ addressInfo?.implementations || undefined }
/>
) }
{ data?.compiler_settings ? (
......
import { Flex, Button, chakra, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Image, useColorModeValue } from '@chakra-ui/react';
import {
Flex,
Button,
chakra,
Popover,
PopoverTrigger,
PopoverBody,
PopoverContent,
Image,
Skeleton,
useDisclosure,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
......@@ -8,9 +20,10 @@ import LinkExternal from 'ui/shared/links/LinkExternal';
interface Props {
className?: string;
hash: string;
isLoading?: string;
}
const ContractCodeIde = ({ className, hash }: Props) => {
const ContractCodeIde = ({ className, hash, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const defaultIconColor = useColorModeValue('gray.600', 'gray.500');
......@@ -31,6 +44,10 @@ const ContractCodeIde = ({ className, hash }: Props) => {
});
}, [ defaultIconColor, hash ]);
if (isLoading) {
return <Skeleton h={ 8 } w="92px" borderRadius="base"/>;
}
if (ideLinks.length === 0) {
return null;
}
......
......@@ -11,6 +11,7 @@ import {
PopoverBody,
PopoverContent,
PopoverTrigger,
Skeleton,
StackDivider,
useDisclosure,
VStack,
......@@ -27,6 +28,7 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props {
className?: string;
data: Array<SmartContractExternalLibrary>;
isLoading?: boolean;
}
const Item = (data: SmartContractExternalLibrary) => {
......@@ -34,7 +36,7 @@ const Item = (data: SmartContractExternalLibrary) => {
<Flex flexDir="column" py={ 2 } w="100%" rowGap={ 1 }>
<Box>{ data.name }</Box>
<AddressEntity
address={{ hash: data.address_hash, is_contract: true, implementation_name: null }}
address={{ hash: data.address_hash, is_contract: true }}
query={{ tab: 'contract' }}
fontSize="sm"
fontWeight="500"
......@@ -44,10 +46,14 @@ const Item = (data: SmartContractExternalLibrary) => {
);
};
const ContractExternalLibraries = ({ className, data }: Props) => {
const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const isMobile = useIsMobile();
if (isLoading) {
return <Skeleton h={ 8 } w="150px" borderRadius="base"/>;
}
if (data.length === 0) {
return null;
}
......@@ -80,6 +86,8 @@ const ContractExternalLibraries = ({ className, data }: Props) => {
divider={ <StackDivider borderColor="divider"/> }
spacing={ 2 }
mt={ 4 }
maxH={{ lg: '50vh' }}
overflowY="scroll"
>
{ data.map((item) => <Item key={ item.address_hash } { ...item }/>) }
</VStack>
......
import { Flex } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { Address as TAddress } from 'types/api/address';
import { getResourceKey } from 'lib/api/useApiQuery';
import ContainerWithScrollY from 'ui/shared/ContainerWithScrollY';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props {
......@@ -17,14 +18,31 @@ const ContractImplementationAddress = ({ hash }: Props) => {
pathParams: { hash },
}));
if (!data?.implementation_address) {
if (!data?.implementations || data.implementations.length === 0) {
return null;
}
const label = data.implementations.length > 1 ? 'Implementation addresses:' : 'Implementation address:';
return (
<Flex mb={ 6 } flexWrap="wrap" columnGap={ 2 }>
<span>Implementation address:</span>
<AddressEntity address={{ hash: data.implementation_address, is_contract: true }} noIcon noCopy/>
<Flex mb={ 6 } flexWrap="wrap" columnGap={ 2 } rowGap={ 2 }>
<span>{ label }</span>
<Box position="relative" maxW="100%">
<ContainerWithScrollY
gradientHeight={ 24 }
rowGap={ 2 }
maxH="150px"
>
{ data.implementations.map((item) => (
<AddressEntity
key={ item.address }
address={{ hash: item.address, is_contract: true }}
noIcon
noCopy
/>
)) }
</ContainerWithScrollY>
</Box>
</Flex>
);
};
......
......@@ -11,8 +11,6 @@ import LinkExternal from 'ui/shared/links/LinkExternal';
import ContractSubmitAuditForm from './contractSubmitAuditForm/ContractSubmitAuditForm';
const SCROLL_GRADIENT_HEIGHT = 24;
type Props = {
addressHash?: string;
}
......@@ -27,17 +25,6 @@ const ContractSecurityAudits = ({ addressHash }: Props) => {
},
});
const containerRef = React.useRef<HTMLDivElement>(null);
const [ hasScroll, setHasScroll ] = React.useState(false);
React.useEffect(() => {
if (!containerRef.current) {
return;
}
setHasScroll(containerRef.current.scrollHeight >= containerRef.current.clientHeight + SCROLL_GRADIENT_HEIGHT / 2);
}, []);
const formTitle = 'Submit audit';
const modalProps = useDisclosure();
......@@ -52,12 +39,10 @@ const ContractSecurityAudits = ({ addressHash }: Props) => {
{ data?.items && data.items.length > 0 && (
<Box position="relative">
<ContainerWithScrollY
gradientHeight={ SCROLL_GRADIENT_HEIGHT }
hasScroll={ hasScroll }
gradientHeight={ 24 }
rowGap={ 1 }
w="100%"
maxH="80px"
ref={ containerRef }
mt={ 2 }
>
{ data.items.map(item => (
......
import { Box, Flex, Select, Skeleton, Text, Tooltip } from '@chakra-ui/react';
import { Flex, Select, Skeleton, Text, Tooltip } from '@chakra-ui/react';
import React from 'react';
import type { AddressImplementation } from 'types/api/addressParams';
import type { SmartContract } from 'types/api/contract';
import type { ArrayElement } from 'types/utils';
import { route } from 'nextjs-routes';
......@@ -16,12 +16,6 @@ import formatFilePath from 'ui/shared/monaco/utils/formatFilePath';
import ContractCodeIdes from './ContractCodeIdes';
import ContractExternalLibraries from './ContractExternalLibraries';
const SOURCE_CODE_OPTIONS = [
{ id: 'primary', label: 'Proxy' } as const,
{ id: 'secondary', label: 'Implementation' } as const,
];
type SourceCodeType = ArrayElement<typeof SOURCE_CODE_OPTIONS>['id'];
function getEditorData(contractInfo: SmartContract | undefined) {
if (!contractInfo || !contractInfo.source_code) {
return undefined;
......@@ -44,152 +38,130 @@ function getEditorData(contractInfo: SmartContract | undefined) {
];
}
interface SourceContractOption {
address: string;
label: string;
}
interface Props {
address?: string;
implementationAddress?: string;
address: string;
implementations?: Array<AddressImplementation>;
}
const ContractSourceCode = ({ address, implementationAddress }: Props) => {
const [ sourceType, setSourceType ] = React.useState<SourceCodeType>('primary');
export const ContractSourceCode = ({ address, implementations }: Props) => {
const primaryContractQuery = useApiQuery('contract', {
pathParams: { hash: address },
queryOptions: {
enabled: Boolean(address),
refetchOnMount: false,
placeholderData: stubs.CONTRACT_CODE_VERIFIED,
},
});
const options: Array<SourceContractOption> = React.useMemo(() => {
return [
{ label: 'Proxy', address },
...(implementations || [])
.filter((item) => item.name && item.address !== address)
.map(({ name, address }, item, array) => ({ address, label: array.length === 1 ? 'Implementation' : `Impl: ${ name }` })),
];
}, [ address, implementations ]);
const [ sourceContract, setSourceContract ] = React.useState<SourceContractOption>(options[0]);
const secondaryContractQuery = useApiQuery('contract', {
pathParams: { hash: implementationAddress },
const contractQuery = useApiQuery('contract', {
pathParams: { hash: sourceContract.address },
queryOptions: {
enabled: Boolean(implementationAddress),
refetchOnMount: false,
placeholderData: stubs.CONTRACT_CODE_VERIFIED,
},
});
const isLoading = implementationAddress ?
primaryContractQuery.isPlaceholderData || secondaryContractQuery.isPlaceholderData :
primaryContractQuery.isPlaceholderData;
const editorData = React.useMemo(() => {
return getEditorData(contractQuery.data);
}, [ contractQuery.data ]);
const primaryEditorData = React.useMemo(() => {
return getEditorData(primaryContractQuery.data);
}, [ primaryContractQuery.data ]);
const isLoading = contractQuery.isPlaceholderData;
const secondaryEditorData = React.useMemo(() => {
return secondaryContractQuery.isPlaceholderData ? undefined : getEditorData(secondaryContractQuery.data);
}, [ secondaryContractQuery.data, secondaryContractQuery.isPlaceholderData ]);
const activeContract = sourceType === 'secondary' ? secondaryContractQuery.data : primaryContractQuery.data;
const activeContractData = sourceType === 'secondary' ? secondaryEditorData : primaryEditorData;
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
const nextOption = options.find(({ address }) => address === event.target.value);
if (nextOption) {
setSourceContract(nextOption);
}
}, [ options ]);
const heading = (
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span>
{ activeContract?.language &&
<Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ activeContract.language })</Text> }
{ contractQuery.data?.language &&
<Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ contractQuery.data.language })</Text> }
</Skeleton>
);
const diagramLinkAddress = (() => {
if (!activeContract?.can_be_visualized_via_sol2uml) {
return;
}
return sourceType === 'secondary' ? implementationAddress : address;
})();
const select = options.length > 1 ? (
<Select
size="xs"
value={ sourceContract.address }
onChange={ handleSelectChange }
w="auto"
maxW={{ lg: '200px', xl: '400px' }}
whiteSpace="nowrap"
textOverflow="ellipsis"
fontWeight={ 600 }
borderRadius="base"
>
{ options.map((option) => <option key={ option.address } value={ option.address }>{ option.label }</option>) }
</Select>
) : null;
const diagramLink = diagramLinkAddress ? (
const externalLibraries = contractQuery.data?.external_libraries ?
<ContractExternalLibraries data={ contractQuery.data.external_libraries } isLoading={ isLoading }/> :
null;
const diagramLink = contractQuery?.data?.can_be_visualized_via_sol2uml ? (
<Tooltip label="Visualize contract code using Sol2Uml JS library">
<LinkInternal
href={ route({ pathname: '/visualize/sol2uml', query: { address: diagramLinkAddress } }) }
href={ route({ pathname: '/visualize/sol2uml', query: { address: sourceContract.address } }) }
ml={{ base: '0', lg: 'auto' }}
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
View UML diagram
View UML diagram
</Skeleton>
</LinkInternal>
</Tooltip>
) : null;
const copyToClipboard = activeContractData?.length === 1 ?
<CopyToClipboard text={ activeContractData[0].source_code } isLoading={ isLoading } ml={{ base: 'auto', lg: diagramLink ? '0' : 'auto' }}/> :
null;
const ides = sourceType === 'secondary' ? <ContractCodeIdes hash={ implementationAddress }/> : <ContractCodeIdes hash={ address }/>;
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSourceType(event.target.value as SourceCodeType);
}, []);
const editorSourceTypeSelector = !secondaryContractQuery.isPlaceholderData && secondaryContractQuery.data?.source_code ? (
<Select
size="xs"
value={ sourceType }
onChange={ handleSelectChange }
w="auto"
fontWeight={ 600 }
borderRadius="base"
>
{ SOURCE_CODE_OPTIONS.map((option) => <option key={ option.id } value={ option.id }>{ option.label }</option>) }
</Select>
) : null;
const externalLibraries = (() => {
if (sourceType === 'secondary') {
return secondaryContractQuery.data?.external_libraries && <ContractExternalLibraries data={ secondaryContractQuery.data.external_libraries }/>;
}
const ides = <ContractCodeIdes hash={ sourceContract.address } isLoading={ isLoading }/>;
return primaryContractQuery.data?.external_libraries && <ContractExternalLibraries data={ primaryContractQuery.data.external_libraries }/>;
})();
const copyToClipboard = contractQuery.data && editorData?.length === 1 ? (
<CopyToClipboard
text={ contractQuery.data.source_code }
isLoading={ isLoading }
ml={{ base: 'auto', lg: diagramLink ? '0' : 'auto' }}
/>
) :
null;
const content = (() => {
if (isLoading) {
return <Skeleton h="557px" w="100%"/>;
}
if (!primaryEditorData) {
if (!editorData) {
return null;
}
return (
<>
<Box display={ sourceType === 'primary' ? 'block' : 'none' }>
<CodeEditor
data={ primaryEditorData }
remappings={ primaryContractQuery.data?.compiler_settings?.remappings }
libraries={ primaryContractQuery.data?.external_libraries ?? undefined }
language={ primaryContractQuery.data?.language ?? undefined }
mainFile={ primaryEditorData[0]?.file_path }
contractName={ primaryContractQuery.data?.name || undefined }
/>
</Box>
{ secondaryEditorData && (
<Box display={ sourceType === 'secondary' ? 'block' : 'none' }>
<CodeEditor
data={ secondaryEditorData }
remappings={ secondaryContractQuery.data?.compiler_settings?.remappings }
libraries={ secondaryContractQuery.data?.external_libraries ?? undefined }
language={ secondaryContractQuery.data?.language ?? undefined }
mainFile={ secondaryEditorData?.[0]?.file_path }
contractName={ secondaryContractQuery.data?.name || undefined }
/>
</Box>
) }
</>
<CodeEditor
key={ sourceContract.address }
data={ editorData }
remappings={ contractQuery.data?.compiler_settings?.remappings }
libraries={ contractQuery.data?.external_libraries ?? undefined }
language={ contractQuery.data?.language ?? undefined }
mainFile={ editorData[0]?.file_path }
contractName={ contractQuery.data?.name ?? undefined }
/>
);
})();
if (!primaryEditorData) {
return null;
}
return (
<section>
<Flex alignItems="center" mb={ 3 } columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
{ heading }
{ editorSourceTypeSelector }
{ select }
{ externalLibraries }
{ diagramLink }
{ ides }
......
import React from 'react';
import type { AddressImplementation } from 'types/api/addressParams';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props {
data: Array<AddressImplementation>;
isLoading?: boolean;
}
const AddressImplementations = ({ data, isLoading }: Props) => {
const hasManyItems = data.length > 1;
const [ hasScroll, setHasScroll ] = React.useState(false);
return (
<>
<DetailsInfoItem.Label
hint={ `Implementation${ hasManyItems ? 's' : '' } address${ hasManyItems ? 'es' : '' } of the proxy contract` }
isLoading={ isLoading }
hasScroll={ hasScroll }
>
{ `Implementation${ hasManyItems ? 's' : '' }` }
</DetailsInfoItem.Label>
<DetailsInfoItem.ValueWithScroll
gradientHeight={ 48 }
onScrollVisibilityChange={ setHasScroll }
rowGap={ 2 }
maxH="200px"
>
{ data.map((item) => (
<AddressEntity
key={ item.address }
address={{ hash: item.address, name: item.name, is_contract: true }}
isLoading={ isLoading }
noIcon
/>
)) }
</DetailsInfoItem.ValueWithScroll>
</>
);
};
export default React.memo(AddressImplementations);
......@@ -62,6 +62,7 @@ const AddressNetWorth = ({ addressData, isLoading, addressHash }: Props) => {
display: 'flex',
alignItems: 'center',
fontSize: 'sm',
lineHeight: 5,
fontWeight: 500,
onClick: onMultichainClick,
};
......
......@@ -79,8 +79,7 @@ export default function useAddressQuery({ hash }: Params): AddressQuery {
has_token_transfers: false,
has_tokens: false,
has_validated_blocks: false,
implementation_address: null,
implementation_name: null,
implementations: null,
is_contract: false,
is_verified: false,
name: null,
......
......@@ -67,7 +67,7 @@ const OptimisticDepositsListItem = ({ item, isLoading }: Props) => {
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn origin</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntityL1
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, implementation_name: '', ens_domain_name: null }}
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, ens_domain_name: null }}
isLoading={ isLoading }
noCopy
truncation="constant"
......
......@@ -59,7 +59,7 @@ const OptimisticDepositsTableItem = ({ item, isLoading }: Props) => {
</Td>
<Td verticalAlign="middle">
<AddressEntityL1
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, implementation_name: '', ens_domain_name: null }}
address={{ hash: item.l1_tx_origin, name: '', is_contract: false, is_verified: false, ens_domain_name: null }}
isLoading={ isLoading }
truncation="constant"
noCopy
......
......@@ -198,7 +198,7 @@ const AddressPageContent = () => {
config.features.validators.isEnabled && addressQuery.data?.has_validated_blocks ?
{ slug: 'validator', name: 'Validator', tagType: 'custom' as const, ordinal: 10 } :
undefined,
addressQuery.data?.implementation_address ? { slug: 'proxy', name: 'Proxy', tagType: 'custom' as const, ordinal: -1 } : undefined,
addressQuery.data?.implementations?.length ? { slug: 'proxy', name: 'Proxy', tagType: 'custom' as const, ordinal: -1 } : undefined,
addressQuery.data?.token ? { slug: 'token', name: 'Token', tagType: 'custom' as const, ordinal: -1 } : undefined,
isSafeAddress ? { slug: 'safe', name: 'Multisig: Safe', tagType: 'custom' as const, ordinal: -10 } : undefined,
config.features.userOps.isEnabled && userOpsAccountQuery.data ?
......
......@@ -86,7 +86,7 @@ const ContractVerificationForAddress = () => {
backLink={ backLink }
/>
<AddressEntity
address={{ hash, is_contract: true, implementation_name: null }}
address={{ hash, is_contract: true }}
noLink
fontFamily="heading"
fontSize="lg"
......
......@@ -35,7 +35,7 @@ const Sol2Uml = () => {
<Flex mb={ 10 } flexWrap="wrap" columnGap={ 3 }>
<span>For contract</span>
<AddressEntity
address={{ hash: addressHash, is_contract: true, implementation_name: null }}
address={{ hash: addressHash, is_contract: true }}
/>
</Flex>
<Sol2UmlDiagram addressHash={ addressHash }/>
......
......@@ -136,7 +136,7 @@ const TokenInstanceContent = () => {
const address = {
hash: hash || '',
is_contract: true,
implementation_name: null,
implementations: null,
watchlist_names: [],
watchlist_address_id: null,
};
......
......@@ -83,7 +83,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
is_contract: data.type === 'contract',
is_verified: data.is_smart_contract_verified,
name: null,
implementation_name: null,
implementations: null,
ens_domain_name: null,
};
......
......@@ -103,7 +103,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
is_contract: data.type === 'contract',
is_verified: data.is_smart_contract_verified,
name: null,
implementation_name: null,
implementations: null,
ens_domain_name: null,
};
const expiresText = data.ens_info?.expiry_date ? ` (expires ${ dayjs(data.ens_info.expiry_date).fromNow() })` : '';
......
import { Flex, useColorModeValue, chakra } from '@chakra-ui/react';
import React from 'react';
type Props = {
export type Props = {
children: React.ReactNode;
containerId?: string;
gradientHeight: number;
className?: string;
hasScroll: boolean;
onScrollVisibilityChange?: (isVisible: boolean) => void;
}
const ContainerWithScrollY = ({ className, hasScroll, containerId, gradientHeight, children }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
const ContainerWithScrollY = ({ className, gradientHeight, children, onScrollVisibilityChange }: Props) => {
const ref = React.useRef<HTMLDivElement>(null);
const [ hasScroll, setHasScroll ] = React.useState(false);
React.useEffect(() => {
if (!ref.current) {
return;
}
const hasScroll = ref.current.scrollHeight >= ref.current.clientHeight + gradientHeight / 2;
setHasScroll(hasScroll);
onScrollVisibilityChange?.(hasScroll);
}, [ gradientHeight, onScrollVisibilityChange ]);
const gradientStartColor = useColorModeValue('whiteAlpha.600', 'blackAlpha.600');
const gradientEndColor = useColorModeValue('whiteAlpha.900', 'blackAlpha.900');
return (
<Flex
id={ containerId }
flexDirection="column"
className={ className }
overflowY={ hasScroll ? 'scroll' : 'auto' }
......@@ -37,4 +48,4 @@ const ContainerWithScrollY = ({ className, hasScroll, containerId, gradientHeigh
);
};
export default chakra(React.forwardRef(ContainerWithScrollY));
export default chakra(ContainerWithScrollY);
import { Text } from '@chakra-ui/react';
import React from 'react';
import ContainerWithScrollY from 'ui/shared/ContainerWithScrollY';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
export const TX_ACTIONS_BLOCK_ID = 'tx-actions';
const SCROLL_GRADIENT_HEIGHT = 48;
type Props = {
children: React.ReactNode;
......@@ -14,42 +11,28 @@ type Props = {
}
const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const [ hasScroll, setHasScroll ] = React.useState(false);
React.useEffect(() => {
if (!containerRef.current) {
return;
}
setHasScroll(containerRef.current.scrollHeight >= containerRef.current.clientHeight + SCROLL_GRADIENT_HEIGHT / 2);
}, []);
return (
<>
<DetailsInfoItem.Label
id={ TX_ACTIONS_BLOCK_ID }
hint={ `Highlighted events of the ${ type === 'tx' ? 'transaction' : 'user operation' }` }
isLoading={ isLoading }
hasScroll={ hasScroll }
>
<span>{ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }</span>
{ hasScroll && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">Scroll to see more</Text> }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
position="relative"
<DetailsInfoItem.ValueWithScroll
gradientHeight={ 48 }
onScrollVisibilityChange={ setHasScroll }
alignItems="stretch"
rowGap={ 5 }
w="100%"
maxH="200px"
>
<ContainerWithScrollY
containerId={ TX_ACTIONS_BLOCK_ID }
gradientHeight={ SCROLL_GRADIENT_HEIGHT }
hasScroll={ hasScroll }
alignItems="stretch"
rowGap={ 5 }
w="100%"
maxH="200px"
ref={ containerRef }
>
{ children }
</ContainerWithScrollY>
</DetailsInfoItem.Value>
{ children }
</DetailsInfoItem.ValueWithScroll>
</>
);
......
import { chakra, GridItem, Flex, Skeleton } from '@chakra-ui/react';
import { chakra, GridItem, Flex, Text, Skeleton } from '@chakra-ui/react';
import React from 'react';
import * as ContainerWithScrollY from 'ui/shared/ContainerWithScrollY';
import Hint from 'ui/shared/Hint';
const LabelScrollText = () => (
<Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">
Scroll to see more
</Text>
);
interface LabelProps {
hint?: string;
children: React.ReactNode;
isLoading?: boolean;
className?: string;
id?: string;
hasScroll?: boolean;
}
const Label = chakra(({ hint, children, isLoading, id, className }: LabelProps) => {
const Label = chakra(({ hint, children, isLoading, id, className, hasScroll }: LabelProps) => {
return (
<GridItem
id={ id }
......@@ -24,6 +32,7 @@ const Label = chakra(({ hint, children, isLoading, id, className }: LabelProps)
{ hint && <Hint label={ hint } isLoading={ isLoading } my={{ lg: '2px' }}/> }
<Skeleton isLoaded={ !isLoading } fontWeight={{ base: 700, lg: 500 }}>
{ children }
{ hasScroll && <LabelScrollText/> }
</Skeleton>
</Flex>
</GridItem>
......@@ -53,7 +62,22 @@ const Value = chakra(({ children, className }: ValueProps) => {
);
});
const ValueWithScroll = chakra(({ children, gradientHeight, onScrollVisibilityChange, className }: ContainerWithScrollY.Props) => {
return (
<Value position="relative">
<ContainerWithScrollY.default
className={ className }
gradientHeight={ gradientHeight }
onScrollVisibilityChange={ onScrollVisibilityChange }
>
{ children }
</ContainerWithScrollY.default>
</Value>
);
});
export {
Label,
Value,
ValueWithScroll,
};
......@@ -25,7 +25,7 @@ export function getTxCourseType(from: string, to: string | undefined, current?:
export const unknownAddress: Omit<AddressParam, 'hash'> = {
is_contract: false,
is_verified: false,
implementation_name: '',
implementations: null,
name: '',
private_tags: [],
public_tags: [],
......
......@@ -86,14 +86,12 @@ const Icon = (props: IconProps) => {
}
return (
<Tooltip label={ props.address.implementation_name }>
<Flex marginRight={ styles.marginRight }>
<AddressIdenticon
size={ props.iconSize === 'lg' ? 30 : 20 }
hash={ props.address.hash }
/>
</Flex>
</Tooltip>
<Flex marginRight={ styles.marginRight }>
<AddressIdenticon
size={ props.iconSize === 'lg' ? 30 : 20 }
hash={ props.address.hash }
/>
</Flex>
);
};
......@@ -142,7 +140,7 @@ const Copy = (props: CopyProps) => {
const Container = EntityBase.Container;
export interface EntityProps extends EntityBase.EntityBaseProps {
address: Pick<AddressParam, 'hash' | 'name' | 'is_contract' | 'is_verified' | 'implementation_name' | 'ens_domain_name' | 'metadata'>;
address: Pick<AddressParam, 'hash' | 'name' | 'is_contract' | 'is_verified' | 'ens_domain_name' | 'metadata'>;
isSafeAddress?: boolean;
}
......
......@@ -26,7 +26,6 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
is_contract: data.type === 'contract',
name: '',
is_verified: data.is_smart_contract_verified,
implementation_name: null,
ens_domain_name: null,
}}
/>
......
......@@ -29,7 +29,7 @@ const TokenInstanceCreatorAddress = ({ hash }: Props) => {
const creatorAddress = {
hash: addressQuery.data.creator_address_hash,
is_contract: false,
implementation_name: null,
implementations: null,
};
return (
......
......@@ -46,7 +46,6 @@ const TokensTableItem = ({
const tokenAddress: AddressEntityProps['address'] = {
hash: address,
name: '',
implementation_name: null,
is_contract: true,
is_verified: false,
ens_domain_name: null,
......
......@@ -82,7 +82,7 @@ const VerifiedAddressesListItem = ({ item, application, onAdd, onEdit, isLoading
<ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity
address={{ hash: item.contractAddress, is_contract: true, implementation_name: null }}
address={{ hash: item.contractAddress, is_contract: true }}
isLoading={ isLoading }
w="100%"
/>
......
......@@ -68,7 +68,7 @@ const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit, isLoadin
<Tr>
<Td>
<AddressEntity
address={{ hash: item.contractAddress, is_contract: true, implementation_name: null }}
address={{ hash: item.contractAddress, is_contract: true }}
isLoading={ isLoading }
fontWeight="600"
/>
......
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