Commit 341acb38 authored by tom's avatar tom

user ops

parent d13f87b4
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false });
const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/op/[hash]" query={ props.query }>
{ /* <UserOp/> */ }
<UserOp/>
</PageNextJs>
);
};
......
......@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// const UserOps = dynamic(() => import('ui/pages/UserOps'), { ssr: false });
const UserOps = dynamic(() => import('ui/pages/UserOps'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/ops">
{ /* <UserOps/> */ }
<UserOps/>
</PageNextJs>
);
};
......
......@@ -3,6 +3,7 @@ import * as React from 'react';
export interface SwitchProps extends ChakraSwitch.RootProps {
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
labelProps?: ChakraSwitch.LabelProps;
rootRef?: React.Ref<HTMLLabelElement>;
trackLabel?: { on: React.ReactNode; off: React.ReactNode };
thumbLabel?: { on: React.ReactNode; off: React.ReactNode };
......@@ -10,7 +11,7 @@ export interface SwitchProps extends ChakraSwitch.RootProps {
export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
function Switch(props, ref) {
const { inputProps, children, rootRef, trackLabel, thumbLabel, ...rest } =
const { inputProps, children, rootRef, trackLabel, thumbLabel, labelProps, ...rest } =
props;
return (
......@@ -31,7 +32,7 @@ export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
) }
</ChakraSwitch.Control >
{ children != null && (
<ChakraSwitch.Label>{ children }</ChakraSwitch.Label>
<ChakraSwitch.Label { ...labelProps }>{ children }</ChakraSwitch.Label>
) }
</ChakraSwitch.Root>
);
......
......@@ -2,9 +2,9 @@ import { inRange } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
import type { Log } from 'types/api/log';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app';
......@@ -12,11 +12,11 @@ import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import { USER_OP } from 'stubs/userOps';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import TextAd from 'ui/shared/ad/TextAd';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import useTabIndexFromQuery from 'ui/shared/Tabs/useTabIndexFromQuery';
import TxLogs from 'ui/tx/TxLogs';
import TxTokenTransfer from 'ui/tx/TxTokenTransfer';
import useTxQuery from 'ui/tx/useTxQuery';
......@@ -65,7 +65,7 @@ const UserOp = () => {
}
}, [ userOpQuery.data ]);
const tabs: Array<RoutedTab> = React.useMemo(() => ([
const tabs: Array<TabItemRegular> = React.useMemo(() => ([
{ id: 'index', title: 'Details', component: <UserOpDetails query={ userOpQuery }/> },
{
id: 'token_transfers',
......@@ -76,7 +76,7 @@ const UserOp = () => {
{ id: 'raw', title: 'Raw', component: <UserOpRaw rawData={ userOpQuery.data?.raw } isLoading={ userOpQuery.isPlaceholderData }/> },
]), [ userOpQuery, txQuery, filterTokenTransfersByLogIndex, filterLogsByLogIndex ]);
const tabIndex = useTabIndexFromQuery(tabs);
const activeTab = useActiveTabFromQuery(tabs);
const backLink = React.useMemo(() => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/ops');
......@@ -106,8 +106,8 @@ const UserOp = () => {
/>
{ userOpQuery.isPlaceholderData ? (
<>
<TabsSkeleton tabs={ tabs } mt={ 6 }/>
{ tabs[tabIndex]?.component }
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
) :
<RoutedTabs tabs={ tabs }/> }
......
import type { FlexProps } from '@chakra-ui/react';
import { Flex, chakra } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import React from 'react';
export interface Props extends FlexProps {
......@@ -44,4 +44,4 @@ const ContainerWithScrollY = ({ gradientHeight, children, onScrollVisibilityChan
);
};
export default chakra(ContainerWithScrollY);
export default ContainerWithScrollY;
......@@ -73,11 +73,11 @@ export const ItemValue = ({ children, ...rest }: ItemValueProps) => {
);
};
export const ItemValueWithScroll = ({ children, gradientHeight, onScrollVisibilityChange, className }: ContainerWithScrollY.Props) => {
export const ItemValueWithScroll = ({ children, gradientHeight, onScrollVisibilityChange, ...rest }: ContainerWithScrollY.Props) => {
return (
<ItemValue position="relative">
<ItemValue position="relative" >
<ContainerWithScrollY.default
className={ className }
{ ...rest }
gradientHeight={ gradientHeight }
onScrollVisibilityChange={ onScrollVisibilityChange }
>
......
......@@ -63,13 +63,12 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
const UserOpEntity = (props: EntityProps) => {
const partsProps = distributeEntityProps(props);
const content = <Content { ...partsProps.content }/>;
return (
<Container { ...partsProps.container }>
<Icon { ...partsProps.icon }/>
<Link { ...partsProps.link }>
<Content { ...partsProps.content }/>
</Link>
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
<Copy { ...partsProps.copy }/>
</Container>
);
......
......@@ -125,7 +125,7 @@ const TxInterpretationElementByType = (
case 'method': {
return (
<Badge
colorScheme={ value === 'Multicall' ? 'teal' : 'gray' }
colorPalette={ value === 'Multicall' ? 'teal' : 'gray' }
truncated
ml={ 1 }
mr={ 2 }
......
import { Tag } from '@chakra-ui/react';
import React from 'react';
import type { UserOpSponsorType as TUserOpSponsorType } from 'types/api/userOps';
import { Badge } from 'toolkit/chakra/badge';
type Props = {
sponsorType: TUserOpSponsorType;
};
......@@ -22,7 +23,7 @@ const UserOpSponsorType = ({ sponsorType }: Props) => {
case 'wallet_deposit':
text = 'Wallet deposit';
}
return <Tag>{ text }</Tag>;
return <Badge>{ text }</Badge>;
};
export default UserOpSponsorType;
import { chakra, FormLabel, FormControl, Switch } from '@chakra-ui/react';
import { chakra, Flex } from '@chakra-ui/react';
import React from 'react';
import { Switch } from 'toolkit/chakra/switch';
import Hint from 'ui/shared/Hint';
interface Props {
......@@ -22,13 +23,23 @@ const UserOpCallDataSwitch = ({ className, initialValue, isDisabled, onChange }:
}, [ onChange ]);
return (
<FormControl className={ className } display="flex" columnGap={ 2 } ml="auto" w="fit-content">
<FormLabel htmlFor="isExternal" fontSize="sm" lineHeight={ 5 } fontWeight={ 600 } m={ 0 }>
Show external call data
</FormLabel>
<Switch id="isExternal" isChecked={ isChecked } isDisabled={ isDisabled } onChange={ handleChange }/>
<Flex ml="auto" alignItems="center" gap={ 2 }>
<Switch
className={ className }
id="call-data-switch"
checked={ isChecked }
disabled={ isDisabled }
onCheckedChange={ handleChange }
flexDirection="row-reverse"
size="md"
gap={ 2 }
labelProps={{ fontWeight: '600', fontSize: 'sm' }}
>
<chakra.span hideBelow="lg">Show external call data</chakra.span>
<chakra.span hideFrom="lg">External call data</chakra.span>
</Switch>
<Hint label="Inner call data is a predicted decoded call from this user operation"/>
</FormControl>
</Flex>
);
};
......
import { Grid, GridItem, Text, Link } from '@chakra-ui/react';
import { Grid, GridItem, Text } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import type { UserOp } from 'types/api/userOps';
......@@ -12,12 +11,12 @@ import { WEI, WEI_IN_GWEI } from 'lib/consts';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import { space } from 'lib/html-entities';
import { currencyUnits } from 'lib/units';
import { Skeleton } from 'toolkit/chakra/skeleton';
import CutLinkDetails from 'toolkit/components/CutLink/CutLinkDetails';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import Skeleton from 'ui/shared/chakra/Skeleton';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo';
import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
......@@ -35,21 +34,9 @@ interface Props {
query: UseQueryResult<UserOp, ResourceError>;
}
const CUT_LINK_NAME = 'UserOpDetails__cutLink';
const UserOpDetails = ({ query }: Props) => {
const [ isExpanded, setIsExpanded ] = React.useState(false);
const { data, isPlaceholderData, isError, error } = query;
const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag);
scroller.scrollTo(CUT_LINK_NAME, {
duration: 500,
smooth: true,
});
}, []);
if (isError) {
if (error?.status === 400 || isCustomAppError(error)) {
throwOnResourceLoadError({ isError, error });
......@@ -76,7 +63,7 @@ const UserOpDetails = ({ query }: Props) => {
User operation hash
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<Skeleton loading={ isPlaceholderData } overflow="hidden">
<UserOpEntity hash={ data.hash } noIcon noLink noCopy={ false }/>
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -113,7 +100,7 @@ const UserOpDetails = ({ query }: Props) => {
wordBreak="break-all"
whiteSpace="normal"
>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
{ data.revert_reason }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -159,7 +146,7 @@ const UserOpDetails = ({ query }: Props) => {
Gas limit
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
{ BigNumber(data.gas).toFormat() }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -171,7 +158,7 @@ const UserOpDetails = ({ query }: Props) => {
Gas used
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
{ BigNumber(data.gas_used).toFormat() }
</Skeleton>
<Utilization
......@@ -214,185 +201,167 @@ const UserOpDetails = ({ query }: Props) => {
{ config.features.txInterpretation.isEnabled && <UserOpDetailsActions hash={ data.hash } isUserOpDataLoading={ isPlaceholderData }/> }
{ /* CUT */ }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Element name={ CUT_LINK_NAME }>
<Skeleton isLoaded={ !isPlaceholderData } mt={ 6 } display="inline-block">
<Link
fontSize="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ handleCutClick }
>
{ isExpanded ? 'Hide details' : 'View details' }
</Link>
</Skeleton>
</Element>
</GridItem>
{ /* ADDITIONAL INFO */ }
{ isExpanded && !isPlaceholderData && (
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailedInfo.ItemLabel
hint="Gas limit for execution phase"
>
Call gas limit
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ BigNumber(data.call_gas_limit).toFormat() }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Gas limit for verification phase"
>
Verification gas limit
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ BigNumber(data.verification_gas_limit).toFormat() }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Gas to compensate the bundler"
>
Pre-verification gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ BigNumber(data.pre_verification_gas).toFormat() }
</DetailedInfo.ItemValue>
{ !config.UI.views.tx.hiddenFields?.gas_fees && (
<>
<DetailedInfo.ItemLabel
hint="Maximum fee per gas "
>
Max fee per gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Text>{ BigNumber(data.max_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.max_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Maximum priority fee per gas"
>
Max priority fee per gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Text>{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemDivider/>
{ data.aggregator && (
<>
<DetailedInfo.ItemLabel
hint="Helper contract to validate an aggregated signature"
>
Aggregator
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.aggregator }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.aggregator_signature && (
<>
<DetailedInfo.ItemLabel
hint="Aggregator signature"
>
Aggregator signature
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.aggregator_signature }
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemLabel
hint="A node (block builder) that handles User operations"
>
Bundler
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.bundler }/>
</DetailedInfo.ItemValue>
{ data.factory && (
<>
<DetailedInfo.ItemLabel
hint="Smart contract that deploys new smart contract wallets for users"
>
Factory
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.factory }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.paymaster && (
<>
<DetailedInfo.ItemLabel
hint="Contract to sponsor the gas fees for User operations"
>
Paymaster
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.paymaster }/>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemLabel
hint="Type of the gas fees sponsor"
>
Sponsor type
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<UserOpSponsorType sponsorType={ data.sponsor_type }/>
</DetailedInfo.ItemValue>
<DetailedInfo.ItemDivider/>
<DetailedInfo.ItemLabel
hint="Used to validate a User operation along with the nonce during verification"
>
Signature
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue
wordBreak="break-all"
whiteSpace="normal"
>
{ data.signature }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Anti-replay protection; also used as the salt for first-time account creation"
>
Nonce
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue
wordBreak="break-all"
whiteSpace="normal"
>
{ data.nonce }
</DetailedInfo.ItemValue>
<UserOpCallData data={ data }/>
<UserOpDecodedCallData data={ data }/>
</>
) }
<CutLinkDetails loading={ isPlaceholderData } mt={ 6 } gridColumn={{ base: undefined, lg: '1 / 3' }}>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailedInfo.ItemLabel
hint="Gas limit for execution phase"
>
Call gas limit
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ BigNumber(data.call_gas_limit).toFormat() }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Gas limit for verification phase"
>
Verification gas limit
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ BigNumber(data.verification_gas_limit).toFormat() }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Gas to compensate the bundler"
>
Pre-verification gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ BigNumber(data.pre_verification_gas).toFormat() }
</DetailedInfo.ItemValue>
{ !config.UI.views.tx.hiddenFields?.gas_fees && (
<>
<DetailedInfo.ItemLabel
hint="Maximum fee per gas "
>
Max fee per gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Text>{ BigNumber(data.max_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text color="text.secondary" whiteSpace="pre">
{ space }({ BigNumber(data.max_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Maximum priority fee per gas"
>
Max priority fee per gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Text>{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text color="text.secondary" whiteSpace="pre">
{ space }({ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemDivider/>
{ data.aggregator && (
<>
<DetailedInfo.ItemLabel
hint="Helper contract to validate an aggregated signature"
>
Aggregator
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.aggregator }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.aggregator_signature && (
<>
<DetailedInfo.ItemLabel
hint="Aggregator signature"
>
Aggregator signature
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.aggregator_signature }
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemLabel
hint="A node (block builder) that handles User operations"
>
Bundler
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.bundler }/>
</DetailedInfo.ItemValue>
{ data.factory && (
<>
<DetailedInfo.ItemLabel
hint="Smart contract that deploys new smart contract wallets for users"
>
Factory
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.factory }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.paymaster && (
<>
<DetailedInfo.ItemLabel
hint="Contract to sponsor the gas fees for User operations"
>
Paymaster
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressStringOrParam address={ data.paymaster }/>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemLabel
hint="Type of the gas fees sponsor"
>
Sponsor type
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<UserOpSponsorType sponsorType={ data.sponsor_type }/>
</DetailedInfo.ItemValue>
<DetailedInfo.ItemDivider/>
<DetailedInfo.ItemLabel
hint="Used to validate a User operation along with the nonce during verification"
>
Signature
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue
wordBreak="break-all"
whiteSpace="normal"
>
{ data.signature }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Anti-replay protection; also used as the salt for first-time account creation"
>
Nonce
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue
wordBreak="break-all"
whiteSpace="normal"
>
{ data.nonce }
</DetailedInfo.ItemValue>
<UserOpCallData data={ data }/>
<UserOpDecodedCallData data={ data }/>
</CutLinkDetails>
</Grid>
);
};
......
......@@ -2,7 +2,7 @@ import React from 'react';
import type { UserOp } from 'types/api/userOps';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
// order is taken from the ERC-4337 standard
......@@ -27,7 +27,7 @@ const UserOpRaw = ({ rawData, isLoading }: Props) => {
return res;
}, {} as UserOp['raw']), undefined, 4);
return <Skeleton isLoaded={ !isLoading }><RawDataSnippet data={ text }/></Skeleton>;
return <Skeleton loading={ isLoading }><RawDataSnippet data={ text }/></Skeleton>;
};
export default UserOpRaw;
import { Flex, Link } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
// import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
......@@ -8,6 +8,7 @@ import config from 'configs/app';
// import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import { TX_INTERPRETATION } from 'stubs/txInterpretation';
import { Link } from 'toolkit/chakra/link';
import { TX_ACTIONS_BLOCK_ID } from 'ui/shared/DetailedInfo/DetailedInfoActionsWrapper';
import UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import TxInterpretation from 'ui/shared/tx/interpretation/TxInterpretation';
......
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