From 751da19e6dec78007e8a0c2cf3120b1e415cd090 Mon Sep 17 00:00:00 2001 From: tom goriunov Date: Fri, 31 Jan 2025 14:26:11 +0100 Subject: [PATCH] Support EIP-7702 addresses (#2523) * tx info customizations * authorizations tab * implement change for address page * amends --- .github/workflows/deploy-review.yml | 1 + .vscode/tasks.json | 1 + configs/envs/.env.mekong | 31 +++++++++++++ mocks/address/address.ts | 6 +++ tools/preset-sync/index.ts | 1 + types/api/address.ts | 2 + types/api/addressParams.ts | 2 + types/api/contract.ts | 1 + types/api/transaction.ts | 9 ++++ ui/address/AddressDetails.tsx | 1 + ui/address/contract/ContractDetails.tsx | 3 +- .../ContractDetailsAlertProxyPattern.tsx | 4 +- .../contract/methods/ContractMethodsProxy.tsx | 6 ++- .../contract/useContractDetailsTabs.tsx | 2 +- ui/address/contract/useContractTabs.tsx | 8 +++- ui/address/details/AddressImplementations.tsx | 13 ++++-- ui/pages/Address.tsx | 15 ++++-- ui/pages/Transaction.tsx | 4 ++ .../entities/address/AddressEntity.pw.tsx | 10 ++++ ui/shared/entities/address/AddressEntity.tsx | 28 ++++++++---- .../entities/address/AddressIconDelegated.tsx | 28 ++++++++++++ ...lor-mode_delegated-address-dark-mode-1.png | Bin 0 -> 3019 bytes ..._default_delegated-address-dark-mode-1.png | Bin 0 -> 2937 bytes ui/tx/TxAuthorizations.tsx | 43 ++++++++++++++++++ ui/tx/authorizations/TxAuthorizationsList.tsx | 21 +++++++++ .../TxAuthorizationsListItem.tsx | 38 ++++++++++++++++ .../authorizations/TxAuthorizationsTable.tsx | 38 ++++++++++++++++ .../TxAuthorizationsTableItem.tsx | 37 +++++++++++++++ ui/tx/details/TxDetailsOther.tsx | 1 + 29 files changed, 332 insertions(+), 22 deletions(-) create mode 100644 configs/envs/.env.mekong create mode 100644 ui/shared/entities/address/AddressIconDelegated.tsx create mode 100644 ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_dark-color-mode_delegated-address-dark-mode-1.png create mode 100644 ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_delegated-address-dark-mode-1.png create mode 100644 ui/tx/TxAuthorizations.tsx create mode 100644 ui/tx/authorizations/TxAuthorizationsList.tsx create mode 100644 ui/tx/authorizations/TxAuthorizationsListItem.tsx create mode 100644 ui/tx/authorizations/TxAuthorizationsTable.tsx create mode 100644 ui/tx/authorizations/TxAuthorizationsTableItem.tsx diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index fba9827b94..3b050858c0 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -21,6 +21,7 @@ on: - eth_sepolia - eth_goerli - filecoin + - mekong - optimism - optimism_celestia - optimism_sepolia diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2811c40961..64a8f6b1f3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -369,6 +369,7 @@ "eth_goerli", "eth_sepolia", "filecoin", + "mekong", "optimism", "optimism_celestia", "optimism_sepolia", diff --git a/configs/envs/.env.mekong b/configs/envs/.env.mekong new file mode 100644 index 0000000000..5b4afc814b --- /dev/null +++ b/configs/envs/.env.mekong @@ -0,0 +1,31 @@ +# Set of ENVs for Mekong network explorer +# https://mekong.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=mekong" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=mekong.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x7c7d9e09a5e0e6441a81efe57dbcf08848cd18a1f4238e28152faead390066a4 +NEXT_PUBLIC_HAS_BEACON_CHAIN=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ID=7078815900 +NEXT_PUBLIC_NETWORK_NAME=Mekong +NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.mekong.ethpandaops.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=Mekong +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/mocks/address/address.ts b/mocks/address/address.ts index b3ad305aea..a9bd063afc 100644 --- a/mocks/address/address.ts +++ b/mocks/address/address.ts @@ -60,6 +60,12 @@ export const withoutName: AddressParam = { ens_domain_name: null, }; +export const delegated: AddressParam = { + ...withoutName, + is_verified: true, + proxy_type: 'eip7702', +}; + export const token: Address = { hash: hash, implementations: null, diff --git a/tools/preset-sync/index.ts b/tools/preset-sync/index.ts index 07d87510b1..8f17a10b8d 100755 --- a/tools/preset-sync/index.ts +++ b/tools/preset-sync/index.ts @@ -14,6 +14,7 @@ const PRESETS = { garnet: 'https://explorer.garnetchain.com', filecoin: 'https://filecoin.blockscout.com', gnosis: 'https://gnosis.blockscout.com', + mekong: 'https://mekong.blockscout.com', optimism: 'https://optimism.blockscout.com', optimism_celestia: 'https://opcelestia-raspberry.gelatoscout.com', optimism_sepolia: 'https://optimism-sepolia.blockscout.com', diff --git a/types/api/address.ts b/types/api/address.ts index 765561a3fa..ad28a65e4f 100644 --- a/types/api/address.ts +++ b/types/api/address.ts @@ -2,6 +2,7 @@ import type { Transaction } from 'types/api/transaction'; import type { UserTags, AddressImplementation, AddressParam, AddressFilecoinParams } from './addressParams'; import type { Block, EpochRewardsType } from './block'; +import type { SmartContractProxyType } from './contract'; import type { InternalTransaction } from './internalTransaction'; import type { MudWorldSchema, MudWorldTable } from './mudWorlds'; import type { NFTTokenType, TokenInfo, TokenInstance, TokenType } from './token'; @@ -31,6 +32,7 @@ export interface Address extends UserTags { name: string | null; token: TokenInfo | null; watchlist_address_id: number | null; + proxy_type?: SmartContractProxyType | null; } export interface AddressZilliqaParams { diff --git a/types/api/addressParams.ts b/types/api/addressParams.ts index dbddbf73b2..1d148e7a8f 100644 --- a/types/api/addressParams.ts +++ b/types/api/addressParams.ts @@ -1,4 +1,5 @@ import type { AddressMetadataTagApi } from './addressMetadata'; +import type { SmartContractProxyType } from './contract'; export interface AddressImplementation { address: string; @@ -59,6 +60,7 @@ export type AddressParamBasic = { tags: Array; } | null; filecoin?: AddressFilecoinParams; + proxy_type?: SmartContractProxyType | null; }; export type AddressParam = UserTags & AddressParamBasic; diff --git a/types/api/contract.ts b/types/api/contract.ts index f317421e1f..7ecb16d719 100644 --- a/types/api/contract.ts +++ b/types/api/contract.ts @@ -25,6 +25,7 @@ export type SmartContractProxyType = | 'eip1822' | 'eip930' | 'eip2535' + | 'eip7702' | 'master_copy' | 'basic_implementation' | 'basic_get_implementation' diff --git a/types/api/transaction.ts b/types/api/transaction.ts index a7817a126f..db22c1437b 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -105,6 +105,8 @@ export type Transaction = { translation?: NovesTxTranslation; arbitrum?: ArbitrumTransactionData; scroll?: ScrollTransactionData; + // EIP-7702 + authorization_list?: Array; }; type ArbitrumTransactionData = { @@ -206,3 +208,10 @@ export type ScrollTransactionData = { l2_block_status: ScrollL2BlockStatus; queue_index: number; }; + +export interface TxAuthorization { + address: string; + authority: string; + chain_id: number; + nonce: number; +} diff --git a/ui/address/AddressDetails.tsx b/ui/address/AddressDetails.tsx index f17c0072e6..2adb3f5e9a 100644 --- a/ui/address/AddressDetails.tsx +++ b/ui/address/AddressDetails.tsx @@ -170,6 +170,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ) } diff --git a/ui/address/contract/ContractDetails.tsx b/ui/address/contract/ContractDetails.tsx index 29fa7fb691..ab9469fe57 100644 --- a/ui/address/contract/ContractDetails.tsx +++ b/ui/address/contract/ContractDetails.tsx @@ -40,7 +40,8 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) => const addressInfo = queryClient.getQueryData(getResourceKey('address', { pathParams: { hash: addressHash } })); const sourceItems: Array = React.useMemo(() => { - const currentAddressItem = { address: addressHash, name: addressInfo?.name || 'Current contract' }; + const currentAddressDefaultName = addressInfo?.proxy_type === 'eip7702' ? 'Delegate address' : 'Current contract'; + const currentAddressItem = { address: addressHash, name: addressInfo?.name || currentAddressDefaultName }; if (!addressInfo || !addressInfo.implementations || addressInfo.implementations.length === 0) { return [ currentAddressItem ]; } diff --git a/ui/address/contract/alerts/ContractDetailsAlertProxyPattern.tsx b/ui/address/contract/alerts/ContractDetailsAlertProxyPattern.tsx index 78b99e5e03..46d24bb3cd 100644 --- a/ui/address/contract/alerts/ContractDetailsAlertProxyPattern.tsx +++ b/ui/address/contract/alerts/ContractDetailsAlertProxyPattern.tsx @@ -9,11 +9,11 @@ interface Props { type: NonNullable; } -const PROXY_TYPES: Record, { +const PROXY_TYPES: Partial, { name: string; link?: string; description?: string; -}> = { +}>> = { eip1167: { name: 'EIP-1167', link: 'https://eips.ethereum.org/EIPS/eip-1167', diff --git a/ui/address/contract/methods/ContractMethodsProxy.tsx b/ui/address/contract/methods/ContractMethodsProxy.tsx index 99f872e3a8..0d56303997 100644 --- a/ui/address/contract/methods/ContractMethodsProxy.tsx +++ b/ui/address/contract/methods/ContractMethodsProxy.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router'; import React from 'react'; import type { AddressImplementation } from 'types/api/addressParams'; +import type { SmartContractProxyType } from 'types/api/contract'; import useApiQuery from 'lib/api/useApiQuery'; import getQueryParamString from 'lib/router/getQueryParamString'; @@ -18,9 +19,10 @@ import { formatAbi } from './utils'; interface Props { implementations: Array; isLoading?: boolean; + proxyType?: SmartContractProxyType; } -const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }: Props) => { +const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading, proxyType }: Props) => { const router = useRouter(); const sourceAddress = getQueryParamString(router.query.source_address); const tab = getQueryParamString(router.query.tab); @@ -48,7 +50,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }: selectedItem={ selectedItem } onItemSelect={ setSelectedItem } isLoading={ isInitialLoading } - label="Implementation address" + label={ proxyType === 'eip7702' ? 'Delegate address' : 'Implementation address' } mb={ 3 } /> { - const canBeVerified = !data?.is_self_destructed && !data?.is_verified; + const canBeVerified = !data?.is_self_destructed && !data?.is_verified && data?.proxy_type !== 'eip7702'; return React.useMemo(() => { const verificationButton = ( diff --git a/ui/address/contract/useContractTabs.tsx b/ui/address/contract/useContractTabs.tsx index d9b9f3e0c0..f79ce0bc0d 100644 --- a/ui/address/contract/useContractTabs.tsx +++ b/ui/address/contract/useContractTabs.tsx @@ -88,7 +88,13 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder verifiedImplementations.length > 0 && { id: [ 'read_write_proxy' as const, 'read_proxy' as const, 'write_proxy' as const ], title: 'Read/Write proxy', - component: , + component: ( + + ), }, config.features.account.isEnabled && { id: [ 'read_write_custom_methods' as const, 'read_custom_methods' as const, 'write_custom_methods' as const ], diff --git a/ui/address/details/AddressImplementations.tsx b/ui/address/details/AddressImplementations.tsx index 2f5cde364e..550a9ad77b 100644 --- a/ui/address/details/AddressImplementations.tsx +++ b/ui/address/details/AddressImplementations.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { AddressImplementation } from 'types/api/addressParams'; +import type { SmartContractProxyType } from 'types/api/contract'; import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; @@ -8,20 +9,26 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity'; interface Props { data: Array; isLoading?: boolean; + proxyType?: SmartContractProxyType; } -const AddressImplementations = ({ data, isLoading }: Props) => { +const AddressImplementations = ({ data, isLoading, proxyType }: Props) => { const hasManyItems = data.length > 1; const [ hasScroll, setHasScroll ] = React.useState(false); + const text = proxyType === 'eip7702' ? 'Delegate address' : `Implementation${ hasManyItems ? 's' : '' }`; + const hint = proxyType === 'eip7702' ? + 'Account\'s executable code address' : + `Implementation${ hasManyItems ? 's' : '' } address${ hasManyItems ? 'es' : '' } of the proxy contract`; + return ( <> - { `Implementation${ hasManyItems ? 's' : '' }` } + { text } { addressQuery.data?.is_contract ? { id: 'contract', title: () => { + const tabName = addressQuery.data.proxy_type === 'eip7702' ? 'Code' : 'Contract'; + if (addressQuery.data.is_verified) { return ( <> - Contract + { tabName } ); } - return 'Contract'; + return tabName; }, component: ( { config.features.validators.isEnabled && addressQuery.data?.has_validated_blocks ? { slug: 'validator', name: 'Validator', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : undefined, - addressQuery.data?.implementations?.length ? { slug: 'proxy', name: 'Proxy', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : undefined, + addressQuery.data?.implementations?.length && addressQuery.data?.proxy_type !== 'eip7702' ? + { slug: 'proxy', name: 'Proxy', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : + undefined, + addressQuery.data?.implementations?.length && addressQuery.data?.proxy_type === 'eip7702' ? + { slug: 'eip7702', name: 'EOA+code', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : + undefined, addressQuery.data?.token ? { slug: 'token', name: 'Token', tagType: 'custom' as const, ordinal: PREDEFINED_TAG_PRIORITY } : undefined, isSafeAddress ? { slug: 'safe', name: 'Multisig: Safe', tagType: 'custom' as const, ordinal: -10 } : undefined, addressProfileAPIFeature.isEnabled && usernameApiTag ? { @@ -417,7 +424,7 @@ const AddressPageContent = () => { <> { { id: 'logs', title: 'Logs', component: }, { id: 'state', title: 'State', component: }, { id: 'raw_trace', title: 'Raw trace', component: }, + txQuery.data?.authorization_list?.length ? + { id: 'authorizations', title: 'Authorizations', component: } : + undefined, ].filter(Boolean); })(); diff --git a/ui/shared/entities/address/AddressEntity.pw.tsx b/ui/shared/entities/address/AddressEntity.pw.tsx index 066c6e62e4..680b935b52 100644 --- a/ui/shared/entities/address/AddressEntity.pw.tsx +++ b/ui/shared/entities/address/AddressEntity.pw.tsx @@ -154,6 +154,16 @@ test('with ENS', async({ render }) => { await expect(component).toHaveScreenshot(); }); +test('delegated address +@dark-mode', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); +}); + test('with name tag', async({ render }) => { const component = await render( ; @@ -51,7 +52,9 @@ const Icon = (props: IconProps) => { return ; } - if (props.address.is_contract) { + const isDelegatedAddress = props.address.proxy_type === 'eip7702'; + + if (props.address.is_contract && !isDelegatedAddress) { if (props.isSafeAddress) { return ( { ); } + const label = (() => { + if (isDelegatedAddress) { + return props.address.is_verified ? 'EOA + verified code' : 'EOA + code'; + } + })(); + return ( - - - + + + + { isDelegatedAddress && } + + ); }; @@ -97,7 +109,7 @@ const Content = chakra((props: ContentProps) => { const nameTag = props.address.metadata?.tags.find(tag => tag.tagType === 'name')?.name; const nameText = nameTag || props.address.ens_domain_name || props.address.name; - const isProxy = props.address.implementations && props.address.implementations.length > 0; + const isProxy = props.address.implementations && props.address.implementations.length > 0 && props.address.proxy_type !== 'eip7702'; if (isProxy) { return ; diff --git a/ui/shared/entities/address/AddressIconDelegated.tsx b/ui/shared/entities/address/AddressIconDelegated.tsx new file mode 100644 index 0000000000..abf61e1f97 --- /dev/null +++ b/ui/shared/entities/address/AddressIconDelegated.tsx @@ -0,0 +1,28 @@ +import { Box, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +interface Props { + isVerified: boolean; +} + +const AddressIconDelegated = ({ isVerified }: Props) => { + const bgColor = useColorModeValue('var(--chakra-colors-white)', 'var(--chakra-colors-black)'); + const defaultColor = useColorModeValue('gray.500', 'gray.400'); + + return ( + + + + + + + + + ); +}; + +export default React.memo(AddressIconDelegated); diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_dark-color-mode_delegated-address-dark-mode-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_dark-color-mode_delegated-address-dark-mode-1.png new file mode 100644 index 0000000000000000000000000000000000000000..893984501bedf20d2754feb0f11568369fba2a3a GIT binary patch literal 3019 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 N002ovPDHLkV1j9{!nyze literal 0 HcmV?d00001 diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_delegated-address-dark-mode-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_delegated-address-dark-mode-1.png new file mode 100644 index 0000000000000000000000000000000000000000..39d656ffd0e9c1bc3957e40a64c8c6d400a3f080 GIT binary patch literal 2937 zcmV-<3x@QGP)Px=Gf6~2RA_klz5eR|&!nO|WOg@u8-o5vobHDHAymRln?@`=tH$;dK2xO@RMELna^RF_P$9u-`u%! z1ONcW9-5X$V}vDQE;t6p2e*HR7-<_l#e6QUT!gKrBe1D->EuOtWSmad^g|m3p)9CJ zQ7RpadL&mf!J#z_C4te*{o|Hu@Wd< znj+d)utxqv^aNyV|65m^tiCldyAR~P%9|;K6c)zzQDqxV=Hw&+=LyDw`e-Z)@k=M6 zp#ebe|Nb^c+ZVVet2wBM;7tGkq_ML4Y6HaYDUyoe!8zsN>(yjkxm=DulDzrwSH1mO z0x?&1;ywKbIuNmEutwA+HG{ZBw5KGVOHQ}8^zE&-M3radvH$?g>T5ec7=ski?jkOi ze9+vcDXF&pSUq0MmQgD$JAZvK-+NzD#hls0kitUS{s7r-pv19b1UyccTDxGMVtCX@ z0Z304#xfU%PC#ftPB%`tfVZ&Wj@7lAj(lBI^@{o%5&e>XI8 ze=>$ar_4_l(+{myyFK@rGz1AhztCJ$nkzAz{2MBD#J-gjNlbKD7lE*-y3AP0hCFWq zj1v~g)O)+e^f{~e=HaqKH?&!s&KGdNlVc9GH;Xo$FG*kl062%PtE(>PJI4u(6aPw) zBv>Ej9!2$dl5gUNmhA(#0pZ0H18XdB;O5|`ka=1Kz+2QgX=jwvHo$4`wRKmvQYN&D% z({6a61pt*7AIjgHJ|Bkr6&~1q5%LPTVa2@XhRZw!~^LTrInVhabFIjFT2SSk4{&>bX5lX?y-#P+^?attE$*E>#_c0R! z;KBg_5b5Uh>TGtdI2r&z%m&R^S=YERhq=h)L7)s7kKpoG9^A~p%;WXSp+X?5FeZoAd(IdHk$sWXGqZ<fB-VE|W{klYXk09jZxVr-`Q65$~NTFo{sXyw|zV+;XmY3}2o0s(3DE zn$=w;#|=>#Y1LV=T+-6o)>r$%x7eSbE#?6LByHJHW4^Xuc_sfW03eAe$!~8NFS~>= z0RX66K|(G7VX5A-5iUlYyEsa67qXfBKD;|ojldvYT*eIMzt?PWrrJA0}@dCx4{aDFD` zfIOAWJ=|8?G_vgvo<&KtqCjyu-!fz%ffbX)qV_f216I7pgHe5Jf8%ibxFd>57soNV zKk}6mPq0nUTcw#Iau`^6VIge8$bQ_=Hm*rMf8$QzMyC@H8UTDU(DTVa&w|FSD-(me zjg|7CRX0TdV|GSx_GxfCZ-v-<%7~_3gCugGwMD0wt(u7sI)e!|quB}IjStJ{!bfIH z^98+8l=_OqCx%G8#{g0V+h@3$nIa7Dc`ynB7TCJ43~S?r+a-*onKg=~`;LVvh~)!- zWw_#~ZZt#w>7V#g78L-xPk-L23q5Z0Fi+w*%HYq%v`=h>5f;)mwCv!xT|dp6cI3H~ zr-C**L-mpSWODFEMz)Ma^b9t8*H-4?`d)`jmN0u&c>S5dQBi7LsZ>7FSD~`ZRL8M1 zISy6Fm^U5>wO%!Uf+e(?+B;}oo@m|xq0wAA^y#L2<^oIi2oc3%Z~*|Q#vaXq)0#eV z&dG5dnw!N105DDK9awg@P&)Gu;XQiN9^%Ru;_Pf}d!e>}$XhX~|9k|IOJcF`!a~}H zmK}s=)=Cn2Z1Ai|1F`rODfMV|ZCzKFdFp%rHaD9=i3;%A76ACM@rQF>4B?lZz%X3X ze0Y6|icudJ#%hoEUZM&rUl#FE006zJb8iQ&bk{lsk0T}Sz5k_2n6*-Z0RUaXnrdj) zwwS0~8ma9c{JYX*r`aL#$(^Zm006N4=hThn7|FZame0@0jMdoqsV{VwO~3%VVZ_*Y z?q=-(ZTrh>^OjQqs7XABs#V*lb8<1q9kr(7)UYvS?LVG>f&l<@SN}Jy1wYv9CXgZe(D|fD3 zK@BS`v~5V)L0HaVVkk|gpKv%_t5))S_siV?cl8qvmn&&yJasYq8~||RFXh}nmVv|X zZgB>Dh6F&+?}}d!Lk#TC88rc(lb5RWFDzfPI48S3V8qk(qGF9 zyV@%o$FfTjSODCNr|aN$)rG#j9S)2W|4Pw1OxfPxb*t5U)Qjf5A@29Iyk#=;;?;EKJK@l=}!v zMDO6})gDcN1~;T93)ep-4$$NW#k>7}`F8u(ntzcto3|}$-DWo@#%>;R+h?YIG-}xw zzuC4qN%qrRL4?N%{?O+C4Jd^I%w_aNdZ75)J4mAJ-Lik)p7s>!ncL}Ww*r9EX0n^L z&mwm}+qN?b`AJ??Mfe3k@eeM$-7=`V+17b++R(LL!jBeiNn9c0bA?gVNBdMH!Y`cw j!4V;Xhsd{H5rXo6Quwx1$vv-Z00000NkvXXu0mjffR2+= literal 0 HcmV?d00001 diff --git a/ui/tx/TxAuthorizations.tsx b/ui/tx/TxAuthorizations.tsx new file mode 100644 index 0000000000..41b08c2170 --- /dev/null +++ b/ui/tx/TxAuthorizations.tsx @@ -0,0 +1,43 @@ +import { Show, Hide } from '@chakra-ui/react'; +import React from 'react'; + +import DataListDisplay from 'ui/shared/DataListDisplay'; +import TxPendingAlert from 'ui/tx/TxPendingAlert'; +import TxSocketAlert from 'ui/tx/TxSocketAlert'; + +import TxAuthorizationsList from './authorizations/TxAuthorizationsList'; +import TxAuthorizationsTable from './authorizations/TxAuthorizationsTable'; +import type { TxQuery } from './useTxQuery'; + +interface Props { + txQuery: TxQuery; +} + +const TxAuthorizations = ({ txQuery }: Props) => { + + if (!txQuery.isPlaceholderData && !txQuery.isError && !txQuery.data?.status) { + return txQuery.socketStatus ? : ; + } + + const content = ( + <> + + + + + + + + ); + + return ( + + ); +}; + +export default TxAuthorizations; diff --git a/ui/tx/authorizations/TxAuthorizationsList.tsx b/ui/tx/authorizations/TxAuthorizationsList.tsx new file mode 100644 index 0000000000..8e1d48e025 --- /dev/null +++ b/ui/tx/authorizations/TxAuthorizationsList.tsx @@ -0,0 +1,21 @@ +import { Box } from '@chakra-ui/react'; +import React from 'react'; + +import type { TxAuthorization } from 'types/api/transaction'; + +import TxAuthorizationsListItem from './TxAuthorizationsListItem'; + +interface Props { + data: Array | undefined; + isLoading?: boolean; +} + +const TxAuthorizationsList = ({ data, isLoading }: Props) => { + return ( + + { data?.map((item, index) => ) } + + ); +}; + +export default TxAuthorizationsList; diff --git a/ui/tx/authorizations/TxAuthorizationsListItem.tsx b/ui/tx/authorizations/TxAuthorizationsListItem.tsx new file mode 100644 index 0000000000..d9b45d57d7 --- /dev/null +++ b/ui/tx/authorizations/TxAuthorizationsListItem.tsx @@ -0,0 +1,38 @@ +import { HStack } from '@chakra-ui/react'; +import React from 'react'; + +import type { TxAuthorization } from 'types/api/transaction'; + +import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; + +interface Props extends TxAuthorization { + isLoading?: boolean; +} + +const TxAuthorizationsListItem = ({ address, authority, chain_id: chainId, nonce, isLoading }: Props) => { + return ( + + + Address + + + + Authority + + + + Chain + { chainId === Number(config.chain.id) ? 'this' : 'any' } + + + Nonce + { nonce } + + + ); +}; + +export default TxAuthorizationsListItem; diff --git a/ui/tx/authorizations/TxAuthorizationsTable.tsx b/ui/tx/authorizations/TxAuthorizationsTable.tsx new file mode 100644 index 0000000000..30942b7bef --- /dev/null +++ b/ui/tx/authorizations/TxAuthorizationsTable.tsx @@ -0,0 +1,38 @@ +import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; +import React from 'react'; + +import type { TxAuthorization } from 'types/api/transaction'; + +import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; +import { default as Thead } from 'ui/shared/TheadSticky'; + +import TxAuthorizationsTableItem from './TxAuthorizationsTableItem'; + +interface Props { + data: Array | undefined; + isLoading?: boolean; +} + +const TxAuthorizationsTable = ({ data, isLoading }: Props) => { + return ( + + + + + + + + + + + + { data?.map((item, index) => ( + + )) } + +
AddressAuthorityChainNonce
+
+ ); +}; + +export default TxAuthorizationsTable; diff --git a/ui/tx/authorizations/TxAuthorizationsTableItem.tsx b/ui/tx/authorizations/TxAuthorizationsTableItem.tsx new file mode 100644 index 0000000000..96e496a5ef --- /dev/null +++ b/ui/tx/authorizations/TxAuthorizationsTableItem.tsx @@ -0,0 +1,37 @@ +import { Tr, Td } from '@chakra-ui/react'; +import React from 'react'; + +import type { TxAuthorization } from 'types/api/transaction'; + +import config from 'configs/app'; +import Skeleton from 'ui/shared/chakra/Skeleton'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; + +interface Props extends TxAuthorization { + isLoading?: boolean; +} + +const TxAuthorizationsItem = ({ address, authority, chain_id: chainId, nonce, isLoading }: Props) => { + return ( + + + + + + + + + + { chainId === Number(config.chain.id) ? 'this' : 'any' } + + + + + { nonce } + + + + ); +}; + +export default React.memo(TxAuthorizationsItem); diff --git a/ui/tx/details/TxDetailsOther.tsx b/ui/tx/details/TxDetailsOther.tsx index e06265867a..0f8497b184 100644 --- a/ui/tx/details/TxDetailsOther.tsx +++ b/ui/tx/details/TxDetailsOther.tsx @@ -25,6 +25,7 @@ const TxDetailsOther = ({ nonce, type, position, queueIndex }: Props) => { { type } { type === 2 && (EIP-1559) } { type === 3 && (EIP-4844) } + { type === 4 && (EIP-7702) } ), queueIndex !== undefined ? (