Commit 1274f1e4 authored by isstuev's avatar isstuev

add user ops tx interpretation

parent 154cdad6
......@@ -53,6 +53,7 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED='true'
NEXT_PUBLIC_HAS_BEACON_CHAIN=true
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
#meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true
\ No newline at end of file
......@@ -662,6 +662,10 @@ export const RESOURCES = {
path: '/api/v2/proxy/account-abstraction/accounts/:hash',
pathParams: [ 'hash' as const ],
},
user_op_interpretation: {
path: '/api/v2/proxy/account-abstraction/operations/:hash/summary',
pathParams: [ 'hash' as const ],
},
// VALIDATORS
validators: {
......@@ -881,6 +885,7 @@ Q extends 'domains_lookup' ? EnsDomainLookupResponse :
Q extends 'user_ops' ? UserOpsResponse :
Q extends 'user_op' ? UserOp :
Q extends 'user_ops_account' ? UserOpsAccount :
Q extends 'user_op_interpretation'? TxInterpretationResponse :
never;
/* eslint-enable @typescript-eslint/indent */
......
......@@ -13,7 +13,6 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import { USER_OP } from 'stubs/userOps';
import TextAd from 'ui/shared/ad/TextAd';
import UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
......@@ -23,6 +22,7 @@ import TxTokenTransfer from 'ui/tx/TxTokenTransfer';
import useTxQuery from 'ui/tx/useTxQuery';
import UserOpDetails from 'ui/userOp/UserOpDetails';
import UserOpRaw from 'ui/userOp/UserOpRaw';
import UserOpSubHeading from 'ui/userOp/UserOpSubHeading';
const UserOp = () => {
const router = useRouter();
......@@ -90,7 +90,7 @@ const UserOp = () => {
throwOnAbsentParamError(hash);
throwOnResourceLoadError(userOpQuery);
const titleSecondRow = <UserOpEntity hash={ hash } noLink noCopy={ false } fontWeight={ 500 } fontFamily="heading"/>;
const titleSecondRow = <UserOpSubHeading hash={ hash }/>;
return (
<>
......
......@@ -9,9 +9,10 @@ const SCROLL_GRADIENT_HEIGHT = 48;
type Props = {
children: React.ReactNode;
isLoading?: boolean;
type: 'tx' | 'user_op';
}
const TxDetailsActions = ({ children, isLoading }: Props) => {
const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const [ hasScroll, setHasScroll ] = React.useState(false);
......@@ -25,8 +26,8 @@ const TxDetailsActions = ({ children, isLoading }: Props) => {
return (
<DetailsInfoItem
title="Transaction action"
hint="Highlighted events of the transaction"
title={ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }
hint={ `Highlighted events of the ${ type === 'tx' ? 'transaction' : 'user operation' }` }
note={ hasScroll ? 'Scroll to see more' : undefined }
position="relative"
isLoading={ isLoading }
......@@ -47,4 +48,4 @@ const TxDetailsActions = ({ children, isLoading }: Props) => {
);
};
export default React.memo(TxDetailsActions);
export default React.memo(DetailsActionsWrapper);
......@@ -66,7 +66,7 @@ type ContentProps = Omit<EntityBase.ContentBaseProps, 'text'> & Pick<EntityProps
const Content = chakra((props: ContentProps) => {
const nameString = [
!props.onlySymbol && (props.token.name ?? 'Unnamed token'),
props.onlySymbol && (props.token.symbol ?? ''),
props.onlySymbol && (props.token.symbol ?? props.token.name ?? 'Unnamed token'),
props.token.symbol && props.jointSymbol && !props.onlySymbol && `(${ props.token.symbol })`,
].filter(Boolean).join(' ');
......
......@@ -5,10 +5,10 @@ import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { TX_INTERPRETATION } from 'stubs/txInterpretation';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import { TX_ACTIONS_BLOCK_ID } from 'ui/shared/DetailsActionsWrapper';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import { TX_ACTIONS_BLOCK_ID } from 'ui/tx/details/txDetailsActions/TxDetailsActionsWrapper';
import TxInterpretation from 'ui/tx/interpretation/TxInterpretation';
import TxInterpretation from 'ui/shared/tx/interpretation/TxInterpretation';
import type { TxQuery } from './useTxQuery';
......
......@@ -2,10 +2,9 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { TX_INTERPRETATION } from 'stubs/txInterpretation';
import DetailsActionsWrapper from 'ui/shared/DetailsActionsWrapper';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import TxInterpretation from 'ui/tx/interpretation/TxInterpretation';
import TxDetailsActionsWrapper from './TxDetailsActionsWrapper';
import TxInterpretation from 'ui/shared/tx/interpretation/TxInterpretation';
interface Props {
hash?: string;
......@@ -30,7 +29,7 @@ const TxDetailsActionsInterpretation = ({ hash, isTxDataLoading }: Props) => {
return (
<>
<TxDetailsActionsWrapper isLoading={ isTxDataLoading || txInterpretationQuery.isPlaceholderData }>
<DetailsActionsWrapper isLoading={ isTxDataLoading || txInterpretationQuery.isPlaceholderData } type="tx">
{ actions.map((action, index: number) => (
<TxInterpretation
key={ index }
......@@ -39,7 +38,7 @@ const TxDetailsActionsInterpretation = ({ hash, isTxDataLoading }: Props) => {
/>
),
) }
</TxDetailsActionsWrapper>
</DetailsActionsWrapper>
<DetailsInfoItemDivider/>
</>
);
......
......@@ -2,10 +2,10 @@ import React from 'react';
import type { TxAction } from 'types/api/txAction';
import DetailsActionsWrapper from 'ui/shared/DetailsActionsWrapper';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import TxDetailsAction from './TxDetailsAction';
import TxDetailsActionsWrapper from './TxDetailsActionsWrapper';
interface Props {
actions: Array<TxAction>;
......@@ -15,9 +15,9 @@ interface Props {
const TxDetailsActionsRaw = ({ actions, isLoading }: Props) => {
return (
<>
<TxDetailsActionsWrapper isLoading={ isLoading }>
<DetailsActionsWrapper isLoading={ isLoading } type="tx">
{ actions.map((action, index: number) => <TxDetailsAction key={ index } action={ action }/>) }
</TxDetailsActionsWrapper>
</DetailsActionsWrapper>
<DetailsInfoItemDivider/>
</>
);
......
......@@ -26,6 +26,8 @@ import UserOpSponsorType from 'ui/shared/userOps/UserOpSponsorType';
import UserOpStatus from 'ui/shared/userOps/UserOpStatus';
import Utilization from 'ui/shared/Utilization/Utilization';
import UserOpDetailsActions from './UserOpDetailsActions';
interface Props {
query: UseQueryResult<UserOp, ResourceError>;
}
......@@ -168,6 +170,8 @@ const UserOpDetails = ({ query }: Props) => {
<AddressStringOrParam address={ data.entry_point } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
{ config.features.txInterpretation.isEnabled && <UserOpDetailsActions hash={ data.hash } isUserOpDataLoading={ isPlaceholderData }/> }
{ /* CUT */ }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Element name={ CUT_LINK_NAME }>
......
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { TX_INTERPRETATION } from 'stubs/txInterpretation';
import DetailsActionsWrapper from 'ui/shared/DetailsActionsWrapper';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import TxInterpretation from 'ui/shared/tx/interpretation/TxInterpretation';
interface Props {
hash?: string;
isUserOpDataLoading: boolean;
}
const TxDetailsActionsInterpretation = ({ hash, isUserOpDataLoading }: Props) => {
const interpretationQuery = useApiQuery('user_op_interpretation', {
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash) && !isUserOpDataLoading,
placeholderData: TX_INTERPRETATION,
refetchOnMount: false,
},
});
const actions = interpretationQuery.data?.data.summaries;
if (!actions || actions.length < 2) {
return null;
}
return (
<>
<DetailsActionsWrapper isLoading={ isUserOpDataLoading || interpretationQuery.isPlaceholderData } type="user_op">
{ actions.map((action, index: number) => (
<TxInterpretation
key={ index }
summary={ action }
isLoading={ isUserOpDataLoading || interpretationQuery.isPlaceholderData }
/>
),
) }
</DetailsActionsWrapper>
<DetailsInfoItemDivider/>
</>
);
};
export default TxDetailsActionsInterpretation;
import { Flex, Link } from '@chakra-ui/react';
// import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
// import type { UserOp } from 'types/api/userOps';
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 { TX_ACTIONS_BLOCK_ID } from 'ui/shared/DetailsActionsWrapper';
import UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import TxInterpretation from 'ui/shared/tx/interpretation/TxInterpretation';
type Props = {
hash?: string;
// userOpQuery: UseQueryResult<UserOp, ResourceError<unknown>>;
}
const UserOpSubHeading = ({ hash }: Props) => {
const hasInterpretationFeature = config.features.txInterpretation.isEnabled;
const txInterpretationQuery = useApiQuery('user_op_interpretation', {
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash) && hasInterpretationFeature,
placeholderData: TX_INTERPRETATION,
},
});
const hasInterpretation = hasInterpretationFeature &&
(txInterpretationQuery.isPlaceholderData || Boolean(txInterpretationQuery.data?.data.summaries.length));
const hasViewAllInterpretationsLink =
!txInterpretationQuery.isPlaceholderData && txInterpretationQuery.data?.data.summaries && txInterpretationQuery.data?.data.summaries.length > 1;
if (hasInterpretation) {
return (
<Flex mr={{ base: 0, lg: 6 }} flexWrap="wrap" alignItems="center">
<TxInterpretation
summary={ txInterpretationQuery.data?.data.summaries[0] }
isLoading={ txInterpretationQuery.isPlaceholderData }
fontSize="lg"
mr={ hasViewAllInterpretationsLink ? 3 : 0 }
/>
{ hasViewAllInterpretationsLink &&
<Link href={ `#${ TX_ACTIONS_BLOCK_ID }` }>View all</Link> }
</Flex>
);
// fallback will be added later
// } else if (hasInterpretationFeature && userOpQuery.data?.decoded_call_data.method_call && userOpQuery.data?.sender && userOpQuery.data?.to) {
// return (
// <TxInterpretation
// summary={{
// summary_template: `{sender_hash} called {method} on {receiver_hash}`,
// summary_template_variables: {
// sender_hash: {
// type: 'address',
// value: txQuery.data.from,
// },
// method: {
// type: 'method',
// value: txQuery.data.method,
// },
// receiver_hash: {
// type: 'address',
// value: txQuery.data.to,
// },
// },
// }}
// isLoading={ txQuery.isPlaceholderData }
// fontSize="lg"
// />
// );
} else {
return <UserOpEntity hash={ hash } noLink noCopy={ false } fontWeight={ 500 } fontFamily="heading"/>;
}
};
export default UserOpSubHeading;
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