Commit 3bc942b4 authored by Max Alekseenko's avatar Max Alekseenko

Merge branch 'main' into marketplace-admin-api

parents 1603b336 c7197bb4
import buildUrl from './buildUrl';
test('builds URL for resource without path params', () => {
const url = buildUrl('config_backend_version');
expect(url).toBe('https://localhost:3003/api/v2/config/backend-version');
});
test('builds URL for resource with path params', () => {
const url = buildUrl('block', { height_or_hash: '42' });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42');
});
describe('falsy query parameters', () => {
test('leaves "false" as query parameter', () => {
const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: false });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=false');
});
test('leaves "null" as query parameter', () => {
const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null');
});
test('strips out empty string as query parameter', () => {
const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null, sort: '' });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null');
});
test('strips out "undefined" as query parameter', () => {
const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null, sort: undefined });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null');
});
});
test('builds URL with array-like query parameters', () => {
const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: [ '0x11', '0x22' ], sort: 'asc' });
expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx%5B0%5D=0x11&includeTx%5B1%5D=0x22&sort=asc');
});
test('builds URL for resource with custom API endpoint', () => {
const url = buildUrl('token_verified_info', { chainId: '42', hash: '0x11' });
expect(url).toBe('https://localhost:3005/api/v1/chains/42/token-infos/0x11');
});
...@@ -19,7 +19,13 @@ export default function buildUrl<R extends ResourceName>( ...@@ -19,7 +19,13 @@ export default function buildUrl<R extends ResourceName>(
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
// there are some pagination params that can be null or false for the next page // there are some pagination params that can be null or false for the next page
value !== undefined && value !== '' && url.searchParams.append(key, String(value)); if (value !== undefined && value !== '') {
if (Array.isArray(value)) {
value.forEach((v, i) => url.searchParams.append(`${ key }[${ i }]`, String(v)));
} else {
url.searchParams.append(key, String(value));
}
}
}); });
return url.toString(); return url.toString();
......
...@@ -2,10 +2,18 @@ import { createPublicClient, http } from 'viem'; ...@@ -2,10 +2,18 @@ import { createPublicClient, http } from 'viem';
import currentChain from './currentChain'; import currentChain from './currentChain';
export const publicClient = createPublicClient({ export const publicClient = (() => {
chain: currentChain, if (currentChain.rpcUrls.public.http.filter(Boolean).length === 0) {
transport: http(), return;
batch: { }
multicall: true,
}, try {
}); return createPublicClient({
chain: currentChain,
transport: http(),
batch: {
multicall: true,
},
});
} catch (error) {}
})();
...@@ -108,7 +108,7 @@ test.describe('socket', () => { ...@@ -108,7 +108,7 @@ test.describe('socket', () => {
}, },
}; };
const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS }) + '?type='; const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS });
await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({ await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({
status: 200, status: 200,
...@@ -144,7 +144,7 @@ test.describe('socket', () => { ...@@ -144,7 +144,7 @@ test.describe('socket', () => {
}, },
}; };
const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS }) + '?type='; const API_URL_NO_TOKEN = buildApiUrl('address_token_transfers', { hash: CURRENT_ADDRESS });
await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({ await page.route(API_URL_NO_TOKEN, (route) => route.fulfill({
status: 200, status: 200,
......
...@@ -13,8 +13,8 @@ import AddressTokens from './AddressTokens'; ...@@ -13,8 +13,8 @@ import AddressTokens from './AddressTokens';
const ADDRESS_HASH = addressMock.withName.hash; const ADDRESS_HASH = addressMock.withName.hash;
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH }); const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }); const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH });
const API_URL_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH }) + '?type='; const API_URL_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH });
const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH }) + '?type='; const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH });
const nextPageParams = { const nextPageParams = {
items_count: 50, items_count: 50,
......
...@@ -50,6 +50,10 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery { ...@@ -50,6 +50,10 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery {
const rpcQuery = useQuery<RpcResponseType, unknown, Block | null>({ const rpcQuery = useQuery<RpcResponseType, unknown, Block | null>({
queryKey: [ 'RPC', 'block', { heightOrHash } ], queryKey: [ 'RPC', 'block', { heightOrHash } ],
queryFn: async() => { queryFn: async() => {
if (!publicClient) {
return null;
}
const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) }; const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) };
return publicClient.getBlock(blockParams).catch(() => null); return publicClient.getBlock(blockParams).catch(() => null);
}, },
...@@ -86,13 +90,13 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery { ...@@ -86,13 +90,13 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery {
}; };
}, },
placeholderData: GET_BLOCK, placeholderData: GET_BLOCK,
enabled: apiQuery.isError || apiQuery.errorUpdateCount > 0, enabled: publicClient !== undefined && (apiQuery.isError || apiQuery.errorUpdateCount > 0),
retry: false, retry: false,
refetchOnMount: false, refetchOnMount: false,
}); });
React.useEffect(() => { React.useEffect(() => {
if (apiQuery.isPlaceholderData) { if (apiQuery.isPlaceholderData || !publicClient) {
return; return;
} }
...@@ -109,7 +113,7 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery { ...@@ -109,7 +113,7 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery {
} }
}, [ rpcQuery.data, rpcQuery.isPlaceholderData ]); }, [ rpcQuery.data, rpcQuery.isPlaceholderData ]);
const isRpcQuery = Boolean((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0 && rpcQuery.data); const isRpcQuery = Boolean(publicClient && (apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0 && rpcQuery.data);
const query = isRpcQuery ? rpcQuery as UseQueryResult<Block, ResourceError<{ status: number }>> : apiQuery; const query = isRpcQuery ? rpcQuery as UseQueryResult<Block, ResourceError<{ status: number }>> : apiQuery;
return { return {
......
...@@ -63,6 +63,10 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param ...@@ -63,6 +63,10 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param
const rpcQuery = useQuery<RpcResponseType, unknown, BlockTransactionsResponse | null>({ const rpcQuery = useQuery<RpcResponseType, unknown, BlockTransactionsResponse | null>({
queryKey: [ 'RPC', 'block_txs', { heightOrHash } ], queryKey: [ 'RPC', 'block_txs', { heightOrHash } ],
queryFn: async() => { queryFn: async() => {
if (!publicClient) {
return null;
}
const blockParams = heightOrHash.startsWith('0x') ? const blockParams = heightOrHash.startsWith('0x') ?
{ blockHash: heightOrHash as `0x${ string }`, includeTransactions: true } : { blockHash: heightOrHash as `0x${ string }`, includeTransactions: true } :
{ blockNumber: BigInt(heightOrHash), includeTransactions: true }; { blockNumber: BigInt(heightOrHash), includeTransactions: true };
...@@ -125,13 +129,13 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param ...@@ -125,13 +129,13 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param
}; };
}, },
placeholderData: GET_BLOCK_WITH_TRANSACTIONS, placeholderData: GET_BLOCK_WITH_TRANSACTIONS,
enabled: tab === 'txs' && (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0), enabled: publicClient !== undefined && tab === 'txs' && (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0),
retry: false, retry: false,
refetchOnMount: false, refetchOnMount: false,
}); });
React.useEffect(() => { React.useEffect(() => {
if (apiQuery.isPlaceholderData) { if (apiQuery.isPlaceholderData || !publicClient) {
return; return;
} }
...@@ -151,7 +155,7 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param ...@@ -151,7 +155,7 @@ export default function useBlockTxQuery({ heightOrHash, blockQuery, tab }: Param
const isRpcQuery = Boolean(( const isRpcQuery = Boolean((
blockQuery.isDegradedData || blockQuery.isDegradedData ||
((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0) ((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0)
) && rpcQuery.data); ) && rpcQuery.data && publicClient);
const rpcQueryWithPages: QueryWithPagesResult<'block_txs'> = React.useMemo(() => { const rpcQueryWithPages: QueryWithPagesResult<'block_txs'> = React.useMemo(() => {
return { return {
......
...@@ -65,6 +65,10 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab ...@@ -65,6 +65,10 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab
const rpcQuery = useQuery<RpcResponseType, unknown, BlockWithdrawalsResponse | null>({ const rpcQuery = useQuery<RpcResponseType, unknown, BlockWithdrawalsResponse | null>({
queryKey: [ 'RPC', 'block', { heightOrHash } ], queryKey: [ 'RPC', 'block', { heightOrHash } ],
queryFn: async() => { queryFn: async() => {
if (!publicClient) {
return null;
}
const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) }; const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) };
return publicClient.getBlock(blockParams).catch(() => null); return publicClient.getBlock(blockParams).catch(() => null);
}, },
...@@ -89,6 +93,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab ...@@ -89,6 +93,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab
}, },
placeholderData: GET_BLOCK, placeholderData: GET_BLOCK,
enabled: enabled:
publicClient !== undefined &&
tab === 'withdrawals' && tab === 'withdrawals' &&
config.features.beaconChain.isEnabled && config.features.beaconChain.isEnabled &&
(blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0), (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0),
...@@ -97,7 +102,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab ...@@ -97,7 +102,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab
}); });
React.useEffect(() => { React.useEffect(() => {
if (apiQuery.isPlaceholderData) { if (apiQuery.isPlaceholderData || !publicClient) {
return; return;
} }
...@@ -117,7 +122,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab ...@@ -117,7 +122,7 @@ export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab
const isRpcQuery = Boolean(( const isRpcQuery = Boolean((
blockQuery.isDegradedData || blockQuery.isDegradedData ||
((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0) ((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0)
) && rpcQuery.data); ) && rpcQuery.data && publicClient);
const rpcQueryWithPages: QueryWithPagesResult<'block_withdrawals'> = React.useMemo(() => { const rpcQueryWithPages: QueryWithPagesResult<'block_withdrawals'> = React.useMemo(() => {
return { return {
......
...@@ -67,7 +67,7 @@ const AddressPageContent = () => { ...@@ -67,7 +67,7 @@ const AddressPageContent = () => {
const userOpsAccountQuery = useApiQuery('user_ops_account', { const userOpsAccountQuery = useApiQuery('user_ops_account', {
pathParams: { hash }, pathParams: { hash },
queryOptions: { queryOptions: {
enabled: Boolean(hash), enabled: Boolean(hash) && config.features.userOps.isEnabled,
placeholderData: USER_OPS_ACCOUNT, placeholderData: USER_OPS_ACCOUNT,
}, },
}); });
...@@ -160,16 +160,18 @@ const AddressPageContent = () => { ...@@ -160,16 +160,18 @@ const AddressPageContent = () => {
].filter(Boolean); ].filter(Boolean);
}, [ addressQuery.data, contractTabs, addressTabsCountersQuery.data, userOpsAccountQuery.data ]); }, [ addressQuery.data, contractTabs, addressTabsCountersQuery.data, userOpsAccountQuery.data ]);
const isLoading = addressQuery.isPlaceholderData || (config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData);
const tags = ( const tags = (
<EntityTags <EntityTags
data={ addressQuery.data } data={ addressQuery.data }
isLoading={ addressQuery.isPlaceholderData } isLoading={ isLoading }
tagsBefore={ [ tagsBefore={ [
!addressQuery.data?.is_contract ? { label: 'eoa', display_name: 'EOA' } : undefined, !addressQuery.data?.is_contract ? { label: 'eoa', display_name: 'EOA' } : undefined,
addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined, addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined,
addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined, addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined,
isSafeAddress ? { label: 'safe', display_name: 'Multisig: Safe' } : undefined, isSafeAddress ? { label: 'safe', display_name: 'Multisig: Safe' } : undefined,
userOpsAccountQuery.data ? { label: 'user_ops_acc', display_name: 'Smart contract wallet' } : undefined, config.features.userOps.isEnabled && userOpsAccountQuery.data ? { label: 'user_ops_acc', display_name: 'Smart contract wallet' } : undefined,
] } ] }
/> />
); );
...@@ -189,8 +191,6 @@ const AddressPageContent = () => { ...@@ -189,8 +191,6 @@ const AddressPageContent = () => {
}; };
}, [ appProps.referrer ]); }, [ appProps.referrer ]);
const isLoading = addressQuery.isPlaceholderData;
const titleSecondRow = ( const titleSecondRow = (
<Flex alignItems="center" w="100%" columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}> <Flex alignItems="center" w="100%" columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
{ addressQuery.data?.ens_domain_name && ( { addressQuery.data?.ens_domain_name && (
...@@ -241,7 +241,7 @@ const AddressPageContent = () => { ...@@ -241,7 +241,7 @@ const AddressPageContent = () => {
<AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/> <AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/>
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
<Box ref={ tabsScrollRef }></Box> <Box ref={ tabsScrollRef }></Box>
{ (addressQuery.isPlaceholderData || addressTabsCountersQuery.isPlaceholderData || userOpsAccountQuery.isPlaceholderData) ? { (isLoading || addressTabsCountersQuery.isPlaceholderData) ?
<TabsSkeleton tabs={ tabs }/> : <TabsSkeleton tabs={ tabs }/> :
content content
} }
......
...@@ -111,7 +111,7 @@ base.describe('bridged tokens', async() => { ...@@ -111,7 +111,7 @@ base.describe('bridged tokens', async() => {
}); });
test('base view', async({ mount, page }) => { test('base view', async({ mount, page }) => {
await page.route(BRIDGED_TOKENS_API_URL + '?chain_ids=99', (route) => route.fulfill({ await page.route(BRIDGED_TOKENS_API_URL + '?chain_ids%5B0%5D=99', (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify(bridgedFilteredTokens), body: JSON.stringify(bridgedFilteredTokens),
})); }));
......
...@@ -7,6 +7,7 @@ import config from 'configs/app'; ...@@ -7,6 +7,7 @@ import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { publicClient } from 'lib/web3/client';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import EntityTags from 'ui/shared/EntityTags'; import EntityTags from 'ui/shared/EntityTags';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -33,7 +34,7 @@ const TransactionPageContent = () => { ...@@ -33,7 +34,7 @@ const TransactionPageContent = () => {
const txQuery = useTxQuery(); const txQuery = useTxQuery();
const { data, isPlaceholderData, isError, error, errorUpdateCount } = txQuery; const { data, isPlaceholderData, isError, error, errorUpdateCount } = txQuery;
const showDegradedView = (isError || isPlaceholderData) && errorUpdateCount > 0; const showDegradedView = publicClient && (isError || isPlaceholderData) && errorUpdateCount > 0;
const tabs: Array<RoutedTab> = (() => { const tabs: Array<RoutedTab> = (() => {
const detailsComponent = showDegradedView ? const detailsComponent = showDegradedView ?
...@@ -97,8 +98,10 @@ const TransactionPageContent = () => { ...@@ -97,8 +98,10 @@ const TransactionPageContent = () => {
return <RoutedTabs tabs={ tabs }/>; return <RoutedTabs tabs={ tabs }/>;
})(); })();
if (error?.status === 422) { if (isError && !showDegradedView) {
throwOnResourceLoadError({ resource: 'tx', error, isError: true }); if (error?.status === 422 || error?.status === 404) {
throwOnResourceLoadError({ resource: 'tx', error, isError: true });
}
} }
return ( return (
......
import React from 'react'; import React from 'react';
import TestnetWarning from 'ui/shared/alerts/TestnetWarning'; import TestnetWarning from 'ui/shared/alerts/TestnetWarning';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TxInfo from './details/TxInfo'; import TxInfo from './details/TxInfo';
import type { TxQuery } from './useTxQuery'; import type { TxQuery } from './useTxQuery';
...@@ -10,6 +11,10 @@ interface Props { ...@@ -10,6 +11,10 @@ interface Props {
} }
const TxDetails = ({ txQuery }: Props) => { const TxDetails = ({ txQuery }: Props) => {
if (txQuery.isError) {
return <DataFetchAlert/>;
}
return ( return (
<> <>
<TestnetWarning mb={ 6 } isLoading={ txQuery.isPlaceholderData }/> <TestnetWarning mb={ 6 } isLoading={ txQuery.isPlaceholderData }/>
......
...@@ -37,6 +37,10 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => { ...@@ -37,6 +37,10 @@ const TxDetailsDegraded = ({ hash, txQuery }: Props) => {
const query = useQuery<RpcResponseType, unknown, Transaction | null>({ const query = useQuery<RpcResponseType, unknown, Transaction | null>({
queryKey: [ 'RPC', 'tx', { hash } ], queryKey: [ 'RPC', 'tx', { hash } ],
queryFn: async() => { queryFn: async() => {
if (!publicClient) {
throw new Error('No public RPC client');
}
const tx = await publicClient.getTransaction({ hash: hash as `0x${ string }` }); const tx = await publicClient.getTransaction({ hash: hash as `0x${ string }` });
if (!tx) { if (!tx) {
......
...@@ -2,6 +2,7 @@ import React from 'react'; ...@@ -2,6 +2,7 @@ import React from 'react';
import { USER_OPS_ITEM } from 'stubs/userOps'; import { USER_OPS_ITEM } from 'stubs/userOps';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TxPendingAlert from 'ui/tx/TxPendingAlert'; import TxPendingAlert from 'ui/tx/TxPendingAlert';
import TxSocketAlert from 'ui/tx/TxSocketAlert'; import TxSocketAlert from 'ui/tx/TxSocketAlert';
...@@ -28,6 +29,10 @@ const TxUserOps = ({ txQuery }: Props) => { ...@@ -28,6 +29,10 @@ const TxUserOps = ({ txQuery }: Props) => {
return txQuery.socketStatus ? <TxSocketAlert status={ txQuery.socketStatus }/> : <TxPendingAlert/>; return txQuery.socketStatus ? <TxSocketAlert status={ txQuery.socketStatus }/> : <TxPendingAlert/>;
} }
if (txQuery.isError) {
return <DataFetchAlert/>;
}
return <UserOpsContent query={ userOpsQuery } showTx={ false }/>; return <UserOpsContent query={ userOpsQuery } showTx={ false }/>;
}; };
......
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