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