diff --git a/apps/explorer/src/app/app.tsx b/apps/explorer/src/app/app.tsx index 45c8ba396..29e30009a 100644 --- a/apps/explorer/src/app/app.tsx +++ b/apps/explorer/src/app/app.tsx @@ -14,6 +14,7 @@ import { RouterProvider } from 'react-router-dom'; import { router } from './routes/router-config'; import { t } from '@vegaprotocol/i18n'; import { Suspense } from 'react'; +import { useExplorerNodeNamesLazyQuery } from './routes/validators/__generated__/NodeNames'; const splashLoading = ( diff --git a/apps/explorer/src/app/components/links/party-link/party-link.tsx b/apps/explorer/src/app/components/links/party-link/party-link.tsx index 3524cdadf..1187d6092 100644 --- a/apps/explorer/src/app/components/links/party-link/party-link.tsx +++ b/apps/explorer/src/app/components/links/party-link/party-link.tsx @@ -5,18 +5,38 @@ import type { ComponentProps } from 'react'; import Hash from '../hash'; import { t } from '@vegaprotocol/i18n'; import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error'; -import { truncateMiddle } from '@vegaprotocol/ui-toolkit'; +import { Icon, truncateMiddle } from '@vegaprotocol/ui-toolkit'; +import { useExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames'; +import type { ExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames'; export const SPECIAL_CASE_NETWORK_ID = '0000000000000000000000000000000000000000000000000000000000000000'; export const SPECIAL_CASE_NETWORK = 'network'; +export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) { + if (!data || data?.nodesConnection?.edges?.length === 0) { + return id; + } + + const validator = data.nodesConnection.edges?.find((e) => { + return e?.node.pubkey === id; + }); + + if (validator) { + return validator.node.name; + } + + return id; +} + export type PartyLinkProps = Partial> & { id: string; truncate?: boolean; }; const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => { + const { data } = useExplorerNodeNamesQuery(); + // Some transactions will involve the 'network' party, which is alias for '000...000' // The party page does not handle this nicely, so in this case we render the word 'Network' if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) { @@ -37,14 +57,20 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => { ); } + const name = getNameForParty(id, data); + const useName = name !== id; + return ( - - - + + {useName && } + + {useName ? name : } + + ); }; diff --git a/apps/explorer/src/app/components/txs/details/chain-response-code/chain-reponse.code.tsx b/apps/explorer/src/app/components/txs/details/chain-response-code/chain-reponse.code.tsx index 6e6eee29c..55558b6a3 100644 --- a/apps/explorer/src/app/components/txs/details/chain-response-code/chain-reponse.code.tsx +++ b/apps/explorer/src/app/components/txs/details/chain-response-code/chain-reponse.code.tsx @@ -1,4 +1,4 @@ -import { Icon } from '@vegaprotocol/ui-toolkit'; +import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit'; // https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go export const ErrorCodes = new Map([ @@ -17,6 +17,8 @@ interface ChainResponseCodeProps { code: number; hideLabel?: boolean; error?: string; + hideIfOk?: boolean; + small?: boolean; } /** @@ -28,14 +30,21 @@ export const ChainResponseCode = ({ code, hideLabel = false, error, + hideIfOk = false, + small = false, }: ChainResponseCodeProps) => { + if (hideIfOk && code === 0) { + return null; + } + const isSuccess = successCodes.has(code); + const size = small ? 3 : 4; const successColour = - code === 71 ? 'fill-vega-orange' : 'fill-vega-green-600'; + code === 71 ? '!fill-vega-orange' : '!fill-vega-green-600'; const icon = isSuccess ? ( - + ) : ( - + ); const label = ErrorCodes.get(code) || 'Unknown response code'; @@ -44,18 +53,28 @@ export const ChainResponseCode = ({ error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error; return ( -
- - {icon} - - {hideLabel ? null : {label}} - {!hideLabel && !!displayError ? ( - — {displayError} - ) : null} -
+ + Response code: {code} - {label} + + } + > +
+ + {icon} + + {hideLabel ? null : {label}} + {!hideLabel && !!displayError ? ( + + — {displayError} + + ) : null} +
+
); }; diff --git a/apps/explorer/src/app/components/txs/tx-order-type.tsx b/apps/explorer/src/app/components/txs/tx-order-type.tsx index 96f96b19d..7c50d72e6 100644 --- a/apps/explorer/src/app/components/txs/tx-order-type.tsx +++ b/apps/explorer/src/app/components/txs/tx-order-type.tsx @@ -36,9 +36,11 @@ const displayString: StringMap = { UndelegateSubmission: 'Undelegation', KeyRotateSubmission: 'Key Rotation', StateVariableProposal: 'State Variable', + 'State Variable Proposal': 'State Variable', Transfer: 'Transfer', CancelTransfer: 'Cancel Transfer', ValidatorHeartbeat: 'Heartbeat', + 'Validator Heartbeat': 'Heartbeat', 'Batch Market Instructions': 'Batch', }; @@ -172,7 +174,7 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => { return (
{type}
diff --git a/apps/explorer/src/app/components/txs/txs-infinite-list-item.tsx b/apps/explorer/src/app/components/txs/txs-infinite-list-item.tsx index 021d16e34..d554ee23c 100644 --- a/apps/explorer/src/app/components/txs/txs-infinite-list-item.tsx +++ b/apps/explorer/src/app/components/txs/txs-infinite-list-item.tsx @@ -5,6 +5,7 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex import { toHex } from '../search/detect-search'; import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code'; import isNumber from 'lodash/isNumber'; +import { PartyLink } from '../links'; const TRUNCATE_LENGTH = 10; @@ -30,15 +31,15 @@ export const TxsInfiniteListItem = ({ return ( - - - ID:  - + + {isNumber(code) ? ( + + ) : ( + code + )} + - - - By:  - - + + - + - - - Block:  - + - - - Success  - - {isNumber(code) ? ( - - ) : ( - code - )} - ); }; diff --git a/apps/explorer/src/app/components/txs/txs-infinite-list.tsx b/apps/explorer/src/app/components/txs/txs-infinite-list.tsx index d3bcf8544..492ebe927 100644 --- a/apps/explorer/src/app/components/txs/txs-infinite-list.tsx +++ b/apps/explorer/src/app/components/txs/txs-infinite-list.tsx @@ -54,15 +54,14 @@ export const TxsInfiniteList = ({ return ( - - + - - - - + + + diff --git a/apps/explorer/src/app/components/txs/txs-per-block.tsx b/apps/explorer/src/app/components/txs/txs-per-block.tsx index e672447a4..5b11559ee 100644 --- a/apps/explorer/src/app/components/txs/txs-per-block.tsx +++ b/apps/explorer/src/app/components/txs/txs-per-block.tsx @@ -10,6 +10,7 @@ import { ChainResponseCode } from './details/chain-response-code/chain-reponse.c import { getTxsDataUrl } from '../../hooks/use-txs-data'; import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit'; import EmptyList from '../empty-list/empty-list'; +import { PartyLink } from '../links'; interface TxsPerBlockProps { blockHeight: string; @@ -62,12 +63,7 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => { modifier="bordered" className="pr-12 font-mono" > - + diff --git a/apps/explorer/src/app/hooks/use-txs-data.ts b/apps/explorer/src/app/hooks/use-txs-data.ts index 7b89487fb..dd9407f6c 100644 --- a/apps/explorer/src/app/hooks/use-txs-data.ts +++ b/apps/explorer/src/app/hooks/use-txs-data.ts @@ -66,29 +66,15 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => { useEffect(() => { if (data && isNumber(data?.transactions?.length)) { setTxsState((prev) => { - const cursor = - data.transactions[data.transactions.length - 1]?.cursor || ''; - const previousCursor = prev.cursor; return { + ...prev, + cursor: data.transactions.at(-1)?.cursor || '', txsData: data.transactions, - hasMoreTxs: data.transactions.length > 0, - previousCursor, - cursor, }; }); } }, [cursor, setTxsState, data]); - useEffect(() => { - l('', '', 'Initial reset'); - setTxsState((prev) => ({ - txsData: [], - hasMoreTxs: false, - cursor: '', - previousCursor: '', - })); - }, [filters, limit, refetch]); - const nextPage = useCallback(() => { return refetch({ limit, diff --git a/apps/explorer/src/app/routes/layout.tsx b/apps/explorer/src/app/routes/layout.tsx index c11cca676..24215a7ee 100644 --- a/apps/explorer/src/app/routes/layout.tsx +++ b/apps/explorer/src/app/routes/layout.tsx @@ -21,6 +21,7 @@ import { import { Footer } from '../components/footer/footer'; import { Header } from '../components/header'; import { Routes } from './route-names'; +import { useExplorerNodeNamesLazyQuery } from './validators/__generated__/NodeNames'; const DialogsContainer = () => { const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore(); @@ -39,6 +40,7 @@ export const Layout = () => { const isHome = Boolean(useMatch(Routes.HOME)); const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment(); const fixedWidthClasses = 'w-full max-w-[1500px] mx-auto'; + const _ = useExplorerNodeNamesLazyQuery(); return ( <> @@ -49,7 +51,7 @@ export const Layout = () => { 'grid grid-rows-[auto_1fr_auto] grid-cols-1', 'border-vega-light-200 dark:border-vega-dark-200', 'antialiased text-black dark:text-white', - 'overflow-hidden relative' + 'relative' )} >
diff --git a/apps/explorer/src/app/routes/parties/id/index.tsx b/apps/explorer/src/app/routes/parties/id/index.tsx index b861fb39a..282eb4ca5 100644 --- a/apps/explorer/src/app/routes/parties/id/index.tsx +++ b/apps/explorer/src/app/routes/parties/id/index.tsx @@ -8,12 +8,19 @@ import { useTxsData } from '../../../hooks/use-txs-data'; import { TxsInfiniteList } from '../../../components/txs'; import { PageHeader } from '../../../components/page-header'; import { useDocumentTitle } from '../../../hooks/use-document-title'; -import { Icon, Intent, Notification, Splash } from '@vegaprotocol/ui-toolkit'; +import { + Button, + Icon, + Intent, + Notification, + Splash, +} from '@vegaprotocol/ui-toolkit'; import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts'; import { PartyBlockStake } from './components/party-block-stake'; import { PartyBlockAccounts } from './components/party-block-accounts'; import { isValidPartyId } from './components/party-id-error'; import { useDataProvider } from '@vegaprotocol/data-provider'; +import { TxsFilter } from '../../../components/txs/tx-filter'; const Party = () => { const { party } = useParams<{ party: string }>(); @@ -25,10 +32,11 @@ const Party = () => { const { isMobile } = useScreenDimensions(); const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]); const filters = `filters[tx.submitter]=${partyId}`; - const { hasMoreTxs, nextPage, error, txsData, loading } = useTxsData({ - limit: 10, - filters, - }); + const { hasMoreTxs, nextPage, previousPage, error, txsData, loading } = + useTxsData({ + limit: 25, + filters, + }); const variables = useMemo(() => ({ partyId }), [partyId]); const { @@ -81,6 +89,26 @@ const Party = () => {
{t('Transactions')} + +
+ + +
+
{!error && txsData ? ( { txs={txsData} loadMoreTxs={nextPage} error={error} - className="mb-28" + className="mb-28 w-full" /> ) : ( diff --git a/apps/explorer/src/app/routes/txs/home/index.tsx b/apps/explorer/src/app/routes/txs/home/index.tsx index 70d4b12d2..0af26b003 100644 --- a/apps/explorer/src/app/routes/txs/home/index.tsx +++ b/apps/explorer/src/app/routes/txs/home/index.tsx @@ -9,7 +9,7 @@ import { useState } from 'react'; import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter'; import { Button } from '@vegaprotocol/ui-toolkit'; -const BE_TXS_PER_REQUEST = 15; +const BE_TXS_PER_REQUEST = 25; export const TxsList = () => { useDocumentTitle(['Transactions']); @@ -73,7 +73,7 @@ export const TxsListFiltered = () => { txs={txsData} loadMoreTxs={nextPage} error={error} - className="mb-28" + className="mb-28 w-full min-w-[400px]" /> ); diff --git a/apps/explorer/src/app/routes/validators/NodeNames.graphql b/apps/explorer/src/app/routes/validators/NodeNames.graphql new file mode 100644 index 000000000..984aaff57 --- /dev/null +++ b/apps/explorer/src/app/routes/validators/NodeNames.graphql @@ -0,0 +1,13 @@ +query ExplorerNodeNames { + nodesConnection { + edges { + node { + id + name + pubkey + tmPubkey + ethereumAddress + } + } + } +} diff --git a/apps/explorer/src/app/routes/validators/__generated__/NodeNames.ts b/apps/explorer/src/app/routes/validators/__generated__/NodeNames.ts new file mode 100644 index 000000000..0f47cb165 --- /dev/null +++ b/apps/explorer/src/app/routes/validators/__generated__/NodeNames.ts @@ -0,0 +1,53 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type ExplorerNodeNamesQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type ExplorerNodeNamesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, pubkey: string, tmPubkey: string, ethereumAddress: string } } | null> | null } }; + + +export const ExplorerNodeNamesDocument = gql` + query ExplorerNodeNames { + nodesConnection { + edges { + node { + id + name + pubkey + tmPubkey + ethereumAddress + } + } + } +} + `; + +/** + * __useExplorerNodeNamesQuery__ + * + * To run a query within a React component, call `useExplorerNodeNamesQuery` and pass it any options that fit your needs. + * When your component renders, `useExplorerNodeNamesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useExplorerNodeNamesQuery({ + * variables: { + * }, + * }); + */ +export function useExplorerNodeNamesQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ExplorerNodeNamesDocument, options); + } +export function useExplorerNodeNamesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ExplorerNodeNamesDocument, options); + } +export type ExplorerNodeNamesQueryHookResult = ReturnType; +export type ExplorerNodeNamesLazyQueryHookResult = ReturnType; +export type ExplorerNodeNamesQueryResult = Apollo.QueryResult; \ No newline at end of file
- {t('Transaction')}   +
+ {t('Txn')}   ID {t('Submitted By')}{t('Type')}{t('Block')}{t('Success')}{t('From')}{t('Type')}{t('Block')}