From 1180a5ff97f04175a6121dc9d973abf5af08242a Mon Sep 17 00:00:00 2001 From: Edd Date: Mon, 12 Dec 2022 11:46:09 +0000 Subject: [PATCH] feat(explorer): delegate/undelegate submission view (#2381) * fix(explorer): initial tx delegation view * fix(explorer): fix dangling duplicate type * feat(explorer): add governanceasset component to show prices correct for that asset * feat(explorer): add undelegate view * feat(explorer): status code component shows error message if available * fix(explorer): remove unused type --- .../asset-balance/Governance-asset.graphql | 5 ++ .../__generated__/Governance-asset.ts | 45 +++++++++++ .../asset-balance/asset-balance.tsx | 4 +- .../governance-asset-balance.tsx | 30 ++++++++ .../links/asset-link/asset-link.tsx | 1 + .../txs/details/Governance-asset.graphql | 0 .../Node-vote.ts | 0 .../txs/details/__generated__/node-vote.ts | 75 ------------------ .../chain-reponse.code.tsx | 5 ++ .../txs/details/shared/tx-details-shared.tsx | 2 +- .../components/txs/details/tx-delegation.tsx | 58 ++++++++++++++ .../txs/details/tx-details-wrapper.tsx | 6 ++ .../components/txs/details/tx-node-vote.tsx | 4 +- .../txs/details/tx-undelegation.tsx | 77 +++++++++++++++++++ .../routes/types/block-explorer-response.d.ts | 1 + 15 files changed, 234 insertions(+), 79 deletions(-) create mode 100644 apps/explorer/src/app/components/asset-balance/Governance-asset.graphql create mode 100644 apps/explorer/src/app/components/asset-balance/__generated__/Governance-asset.ts create mode 100644 apps/explorer/src/app/components/asset-balance/governance-asset-balance.tsx create mode 100644 apps/explorer/src/app/components/txs/details/Governance-asset.graphql rename apps/explorer/src/app/components/txs/details/{__generated___ => __generated__}/Node-vote.ts (100%) delete mode 100644 apps/explorer/src/app/components/txs/details/__generated__/node-vote.ts create mode 100644 apps/explorer/src/app/components/txs/details/tx-delegation.tsx create mode 100644 apps/explorer/src/app/components/txs/details/tx-undelegation.tsx diff --git a/apps/explorer/src/app/components/asset-balance/Governance-asset.graphql b/apps/explorer/src/app/components/asset-balance/Governance-asset.graphql new file mode 100644 index 000000000..d42ebbdbe --- /dev/null +++ b/apps/explorer/src/app/components/asset-balance/Governance-asset.graphql @@ -0,0 +1,5 @@ +query ExplorerGovernanceAsset { + networkParameter(key: "reward.asset") { + value + } +} diff --git a/apps/explorer/src/app/components/asset-balance/__generated__/Governance-asset.ts b/apps/explorer/src/app/components/asset-balance/__generated__/Governance-asset.ts new file mode 100644 index 000000000..666b97170 --- /dev/null +++ b/apps/explorer/src/app/components/asset-balance/__generated__/Governance-asset.ts @@ -0,0 +1,45 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type ExplorerGovernanceAssetQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type ExplorerGovernanceAssetQuery = { __typename?: 'Query', networkParameter?: { __typename?: 'NetworkParameter', value: string } | null }; + + +export const ExplorerGovernanceAssetDocument = gql` + query ExplorerGovernanceAsset { + networkParameter(key: "reward.asset") { + value + } +} + `; + +/** + * __useExplorerGovernanceAssetQuery__ + * + * To run a query within a React component, call `useExplorerGovernanceAssetQuery` and pass it any options that fit your needs. + * When your component renders, `useExplorerGovernanceAssetQuery` 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 } = useExplorerGovernanceAssetQuery({ + * variables: { + * }, + * }); + */ +export function useExplorerGovernanceAssetQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ExplorerGovernanceAssetDocument, options); + } +export function useExplorerGovernanceAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ExplorerGovernanceAssetDocument, options); + } +export type ExplorerGovernanceAssetQueryHookResult = ReturnType; +export type ExplorerGovernanceAssetLazyQueryHookResult = ReturnType; +export type ExplorerGovernanceAssetQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/explorer/src/app/components/asset-balance/asset-balance.tsx b/apps/explorer/src/app/components/asset-balance/asset-balance.tsx index db51d8095..3201fc4f9 100644 --- a/apps/explorer/src/app/components/asset-balance/asset-balance.tsx +++ b/apps/explorer/src/app/components/asset-balance/asset-balance.tsx @@ -13,6 +13,7 @@ export type AssetBalanceProps = { */ const AssetBalance = ({ assetId, price }: AssetBalanceProps) => { const { data } = useExplorerAssetQuery({ + fetchPolicy: 'cache-first', variables: { id: assetId }, }); @@ -23,7 +24,8 @@ const AssetBalance = ({ assetId, price }: AssetBalanceProps) => { return (
- {label} + {label}{' '} + {data?.asset?.id ? : null}
); }; diff --git a/apps/explorer/src/app/components/asset-balance/governance-asset-balance.tsx b/apps/explorer/src/app/components/asset-balance/governance-asset-balance.tsx new file mode 100644 index 000000000..11889990c --- /dev/null +++ b/apps/explorer/src/app/components/asset-balance/governance-asset-balance.tsx @@ -0,0 +1,30 @@ +import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers'; +import { useExplorerGovernanceAssetQuery } from './__generated__/Governance-asset'; +import AssetBalance from './asset-balance'; + +export type GovernanceAssetBalanceProps = { + price: string; +}; + +const DEFAULT_DECIMALS = 18; + +/** + * Effectively a wrapper around AssetBalance that does an extra query to fetch + * the governance asset first, which is set by a network parameter + */ +const GovernanceAssetBalance = ({ price }: GovernanceAssetBalanceProps) => { + const { data } = useExplorerGovernanceAssetQuery(); + + if (data && data.networkParameter?.value) { + const governanceAssetId = data.networkParameter.value; + return ; + } else { + return ( +
+ {addDecimalsFormatNumber(price, DEFAULT_DECIMALS)} +
+ ); + } +}; + +export default GovernanceAssetBalance; diff --git a/apps/explorer/src/app/components/links/asset-link/asset-link.tsx b/apps/explorer/src/app/components/links/asset-link/asset-link.tsx index 271bf1a23..ed124f24c 100644 --- a/apps/explorer/src/app/components/links/asset-link/asset-link.tsx +++ b/apps/explorer/src/app/components/links/asset-link/asset-link.tsx @@ -16,6 +16,7 @@ export type AssetLinkProps = Partial> & { */ const AssetLink = ({ id, ...props }: AssetLinkProps) => { const { data } = useExplorerAssetQuery({ + fetchPolicy: 'cache-first', variables: { id }, }); diff --git a/apps/explorer/src/app/components/txs/details/Governance-asset.graphql b/apps/explorer/src/app/components/txs/details/Governance-asset.graphql new file mode 100644 index 000000000..e69de29bb diff --git a/apps/explorer/src/app/components/txs/details/__generated___/Node-vote.ts b/apps/explorer/src/app/components/txs/details/__generated__/Node-vote.ts similarity index 100% rename from apps/explorer/src/app/components/txs/details/__generated___/Node-vote.ts rename to apps/explorer/src/app/components/txs/details/__generated__/Node-vote.ts diff --git a/apps/explorer/src/app/components/txs/details/__generated__/node-vote.ts b/apps/explorer/src/app/components/txs/details/__generated__/node-vote.ts deleted file mode 100644 index c265cb686..000000000 --- a/apps/explorer/src/app/components/txs/details/__generated__/node-vote.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as Types from '@vegaprotocol/types'; - -import { gql } from '@apollo/client'; -import * as Apollo from '@apollo/client'; -const defaultOptions = {} as const; -export type ExplorerNodeVoteQueryVariables = Types.Exact<{ - id: Types.Scalars['ID']; -}>; - - -export type ExplorerNodeVoteQuery = { __typename?: 'Query', withdrawal?: { __typename?: 'Withdrawal', id: string, status: Types.WithdrawalStatus, createdTimestamp: any, withdrawnTimestamp?: any | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string } } | null, deposit?: { __typename?: 'Deposit', id: string, status: Types.DepositStatus, createdTimestamp: any, creditedTimestamp?: any | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string } } | null }; - - -export const ExplorerNodeVoteDocument = gql` - query ExplorerNodeVote($id: ID!) { - withdrawal(id: $id) { - id - status - createdTimestamp - withdrawnTimestamp - txHash - asset { - id - name - decimals - } - party { - id - } - } - deposit(id: $id) { - id - status - createdTimestamp - creditedTimestamp - txHash - asset { - id - name - decimals - } - party { - id - } - } -} - `; - -/** - * __useExplorerNodeVoteQuery__ - * - * To run a query within a React component, call `useExplorerNodeVoteQuery` and pass it any options that fit your needs. - * When your component renders, `useExplorerNodeVoteQuery` 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 } = useExplorerNodeVoteQuery({ - * variables: { - * id: // value for 'id' - * }, - * }); - */ -export function useExplorerNodeVoteQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(ExplorerNodeVoteDocument, options); - } -export function useExplorerNodeVoteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(ExplorerNodeVoteDocument, options); - } -export type ExplorerNodeVoteQueryHookResult = ReturnType; -export type ExplorerNodeVoteLazyQueryHookResult = ReturnType; -export type ExplorerNodeVoteQueryResult = Apollo.QueryResult; \ No newline at end of file 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 07cc243ee..69d2ed66c 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 @@ -13,6 +13,7 @@ export const successCodes = new Set([0]); interface ChainResponseCodeProps { code: number; hideLabel?: boolean; + error?: string; } /** @@ -23,6 +24,7 @@ interface ChainResponseCodeProps { export const ChainResponseCode = ({ code, hideLabel = false, + error, }: ChainResponseCodeProps) => { const isSuccess = successCodes.has(code); @@ -39,6 +41,9 @@ export const ChainResponseCode = ({ {icon} {hideLabel ? null : {label}} + {!hideLabel && !!error ? ( + — {error} + ) : null} ); }; diff --git a/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx b/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx index cbb837932..690d4b121 100644 --- a/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx +++ b/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx @@ -73,7 +73,7 @@ export const TxDetailsShared = ({ {t('Response code')} - + diff --git a/apps/explorer/src/app/components/txs/details/tx-delegation.tsx b/apps/explorer/src/app/components/txs/details/tx-delegation.tsx new file mode 100644 index 000000000..c2576a6af --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/tx-delegation.tsx @@ -0,0 +1,58 @@ +import { t } from '@vegaprotocol/react-helpers'; +import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; +import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; +import { TxDetailsShared } from './shared/tx-details-shared'; +import { TableCell, TableRow, TableWithTbody } from '../../table'; +import { NodeLink } from '../../links'; +import GovernanceAssetBalance from '../../asset-balance/governance-asset-balance'; +import type { components } from '../../../../types/explorer'; + +interface TxDetailsDelegateProps { + txData: BlockExplorerTransactionResult | undefined; + pubKey: string | undefined; + blockData: TendermintBlocksResponse | undefined; +} + +/** + * There aren't currently good APIs for exploring delegations or epochs so this + * view is currently a little basic - but it gives the key details. + * + * Future improvements could be: + * - Show if the delegation has taken effect or not, based on epoch + * + * The signature can be turned in to an id with txSignatureToDeterministicId but + * for now there are no details to fetch. + */ +export const TxDetailsDelegate = ({ + txData, + pubKey, + blockData, +}: TxDetailsDelegateProps) => { + if (!txData || !txData.command.delegateSubmission) { + return <>{t('Awaiting Block Explorer transaction details')}; + } + const d: components['schemas']['v1DelegateSubmission'] = + txData.command.delegateSubmission; + + return ( + + + {d.nodeId ? ( + + {t('Node')} + + + + + ) : null} + {d.amount ? ( + + {t('Amount')} + + + + + ) : null} + + ); +}; diff --git a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx index 9faf94bc9..8549f9f2c 100644 --- a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx @@ -15,6 +15,8 @@ import { TxDetailsOrderCancel } from './tx-order-cancel'; import get from 'lodash/get'; import { TxDetailsOrderAmend } from './tx-order-amend'; import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission'; +import { TxDetailsDelegate } from './tx-delegation'; +import { TxDetailsUndelegate } from './tx-undelegation'; interface TxDetailsWrapperProps { txData: BlockExplorerTransactionResult | undefined; @@ -91,6 +93,10 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) { return TxDetailsNodeVote; case 'Withdraw': return TxDetailsWithdrawSubmission; + case 'Delegate': + return TxDetailsDelegate; + case 'Undelegate': + return TxDetailsUndelegate; default: return TxDetailsGeneric; } diff --git a/apps/explorer/src/app/components/txs/details/tx-node-vote.tsx b/apps/explorer/src/app/components/txs/details/tx-node-vote.tsx index 53d659454..7265b0a1d 100644 --- a/apps/explorer/src/app/components/txs/details/tx-node-vote.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-node-vote.tsx @@ -3,8 +3,8 @@ import type { BlockExplorerTransactionResult } from '../../../routes/types/block import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import { TxDetailsShared } from './shared/tx-details-shared'; import { TableCell, TableRow, TableWithTbody } from '../../table'; -import type { ExplorerNodeVoteQueryResult } from './__generated___/Node-vote'; -import { useExplorerNodeVoteQuery } from './__generated___/Node-vote'; +import type { ExplorerNodeVoteQueryResult } from './__generated__/Node-vote'; +import { useExplorerNodeVoteQuery } from './__generated__/Node-vote'; import { PartyLink } from '../../links'; import { Time } from '../../time'; import { diff --git a/apps/explorer/src/app/components/txs/details/tx-undelegation.tsx b/apps/explorer/src/app/components/txs/details/tx-undelegation.tsx new file mode 100644 index 000000000..f24986f5d --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/tx-undelegation.tsx @@ -0,0 +1,77 @@ +import { t } from '@vegaprotocol/react-helpers'; +import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response'; +import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; +import { TxDetailsShared } from './shared/tx-details-shared'; +import { TableCell, TableRow, TableWithTbody } from '../../table'; +import { NodeLink } from '../../links'; +import GovernanceAssetBalance from '../../asset-balance/governance-asset-balance'; +import type { components } from '../../../../types/explorer'; + +export const methodText: Record< + components['schemas']['UndelegateSubmissionMethod'], + string +> = { + METHOD_NOW: 'Immediate', + METHOD_UNSPECIFIED: 'Unspecified', + METHOD_AT_END_OF_EPOCH: 'End of epoch', + // This will be removed in a future release + METHOD_IN_ANGER: 'Immediate', +}; + +interface TxDetailsUndelegateProps { + txData: BlockExplorerTransactionResult | undefined; + pubKey: string | undefined; + blockData: TendermintBlocksResponse | undefined; +} + +/** + * There aren't currently good APIs for exploring delegations or epochs so this + * view is currently a little basic - but it gives the key details. + * + * Future improvements could be: + * - Show if the undelegation has taken effect or not, based on epoch + * - Show the the total stake for the node after undelegation takes effect + * + * The signature can be turned in to an id with txSignatureToDeterministicId but + * for now there are no details to fetch. + */ +export const TxDetailsUndelegate = ({ + txData, + pubKey, + blockData, +}: TxDetailsUndelegateProps) => { + if (!txData || !txData.command.undelegateSubmission) { + return <>{t('Awaiting Block Explorer transaction details')}; + } + + const u: components['schemas']['v1UndelegateSubmission'] = + txData.command.undelegateSubmission; + + return ( + + + {u.nodeId ? ( + + {t('Node')} + + + + + ) : null} + {u.amount ? ( + + {t('Amount')} + + + + + ) : null} + {u.method ? ( + + {t('When')} + {methodText[u.method]} + + ) : null} + + ); +}; diff --git a/apps/explorer/src/app/routes/types/block-explorer-response.d.ts b/apps/explorer/src/app/routes/types/block-explorer-response.d.ts index a405f68a6..9254b09f7 100644 --- a/apps/explorer/src/app/routes/types/block-explorer-response.d.ts +++ b/apps/explorer/src/app/routes/types/block-explorer-response.d.ts @@ -13,6 +13,7 @@ export interface BlockExplorerTransactionResult { signature: { value: string; }; + error?: string; } export interface BlockExplorerTransactions {