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
This commit is contained in:
Edd 2022-12-12 11:46:09 +00:00 committed by GitHub
parent 71399105ee
commit 1180a5ff97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 234 additions and 79 deletions

View File

@ -0,0 +1,5 @@
query ExplorerGovernanceAsset {
networkParameter(key: "reward.asset") {
value
}
}

View File

@ -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<ExplorerGovernanceAssetQuery, ExplorerGovernanceAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerGovernanceAssetQuery, ExplorerGovernanceAssetQueryVariables>(ExplorerGovernanceAssetDocument, options);
}
export function useExplorerGovernanceAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerGovernanceAssetQuery, ExplorerGovernanceAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerGovernanceAssetQuery, ExplorerGovernanceAssetQueryVariables>(ExplorerGovernanceAssetDocument, options);
}
export type ExplorerGovernanceAssetQueryHookResult = ReturnType<typeof useExplorerGovernanceAssetQuery>;
export type ExplorerGovernanceAssetLazyQueryHookResult = ReturnType<typeof useExplorerGovernanceAssetLazyQuery>;
export type ExplorerGovernanceAssetQueryResult = Apollo.QueryResult<ExplorerGovernanceAssetQuery, ExplorerGovernanceAssetQueryVariables>;

View File

@ -13,6 +13,7 @@ export type AssetBalanceProps = {
*/ */
const AssetBalance = ({ assetId, price }: AssetBalanceProps) => { const AssetBalance = ({ assetId, price }: AssetBalanceProps) => {
const { data } = useExplorerAssetQuery({ const { data } = useExplorerAssetQuery({
fetchPolicy: 'cache-first',
variables: { id: assetId }, variables: { id: assetId },
}); });
@ -23,7 +24,8 @@ const AssetBalance = ({ assetId, price }: AssetBalanceProps) => {
return ( return (
<div className="inline-block"> <div className="inline-block">
<span>{label}</span> <AssetLink id={data?.asset?.id || ''} /> <span>{label}</span>{' '}
{data?.asset?.id ? <AssetLink id={data.asset.id} /> : null}
</div> </div>
); );
}; };

View File

@ -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 <AssetBalance price={price} assetId={governanceAssetId} />;
} else {
return (
<div className="inline-block">
<span>{addDecimalsFormatNumber(price, DEFAULT_DECIMALS)}</span>
</div>
);
}
};
export default GovernanceAssetBalance;

View File

@ -16,6 +16,7 @@ export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
*/ */
const AssetLink = ({ id, ...props }: AssetLinkProps) => { const AssetLink = ({ id, ...props }: AssetLinkProps) => {
const { data } = useExplorerAssetQuery({ const { data } = useExplorerAssetQuery({
fetchPolicy: 'cache-first',
variables: { id }, variables: { id },
}); });

View File

@ -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<ExplorerNodeVoteQuery, ExplorerNodeVoteQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerNodeVoteQuery, ExplorerNodeVoteQueryVariables>(ExplorerNodeVoteDocument, options);
}
export function useExplorerNodeVoteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerNodeVoteQuery, ExplorerNodeVoteQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerNodeVoteQuery, ExplorerNodeVoteQueryVariables>(ExplorerNodeVoteDocument, options);
}
export type ExplorerNodeVoteQueryHookResult = ReturnType<typeof useExplorerNodeVoteQuery>;
export type ExplorerNodeVoteLazyQueryHookResult = ReturnType<typeof useExplorerNodeVoteLazyQuery>;
export type ExplorerNodeVoteQueryResult = Apollo.QueryResult<ExplorerNodeVoteQuery, ExplorerNodeVoteQueryVariables>;

View File

@ -13,6 +13,7 @@ export const successCodes = new Set([0]);
interface ChainResponseCodeProps { interface ChainResponseCodeProps {
code: number; code: number;
hideLabel?: boolean; hideLabel?: boolean;
error?: string;
} }
/** /**
@ -23,6 +24,7 @@ interface ChainResponseCodeProps {
export const ChainResponseCode = ({ export const ChainResponseCode = ({
code, code,
hideLabel = false, hideLabel = false,
error,
}: ChainResponseCodeProps) => { }: ChainResponseCodeProps) => {
const isSuccess = successCodes.has(code); const isSuccess = successCodes.has(code);
@ -39,6 +41,9 @@ export const ChainResponseCode = ({
{icon} {icon}
</span> </span>
{hideLabel ? null : <span>{label}</span>} {hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!error ? (
<span className="ml-1">&mdash;&nbsp;{error}</span>
) : null}
</div> </div>
); );
}; };

View File

@ -73,7 +73,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Response code')}</TableCell> <TableCell>{t('Response code')}</TableCell>
<TableCell> <TableCell>
<ChainResponseCode code={txData.code} /> <ChainResponseCode code={txData.code} error={txData.error} />
</TableCell> </TableCell>
</TableRow> </TableRow>
</> </>

View File

@ -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 (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{d.nodeId ? (
<TableRow modifier="bordered">
<TableCell>{t('Node')}</TableCell>
<TableCell>
<NodeLink id={d.nodeId} />
</TableCell>
</TableRow>
) : null}
{d.amount ? (
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>
<GovernanceAssetBalance price={d.amount} />
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};

View File

@ -15,6 +15,8 @@ import { TxDetailsOrderCancel } from './tx-order-cancel';
import get from 'lodash/get'; import get from 'lodash/get';
import { TxDetailsOrderAmend } from './tx-order-amend'; import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission'; import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation';
import { TxDetailsUndelegate } from './tx-undelegation';
interface TxDetailsWrapperProps { interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@ -91,6 +93,10 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsNodeVote; return TxDetailsNodeVote;
case 'Withdraw': case 'Withdraw':
return TxDetailsWithdrawSubmission; return TxDetailsWithdrawSubmission;
case 'Delegate':
return TxDetailsDelegate;
case 'Undelegate':
return TxDetailsUndelegate;
default: default:
return TxDetailsGeneric; return TxDetailsGeneric;
} }

View File

@ -3,8 +3,8 @@ import type { BlockExplorerTransactionResult } from '../../../routes/types/block
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response'; import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared'; import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table'; import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { ExplorerNodeVoteQueryResult } from './__generated___/Node-vote'; import type { ExplorerNodeVoteQueryResult } from './__generated__/Node-vote';
import { useExplorerNodeVoteQuery } from './__generated___/Node-vote'; import { useExplorerNodeVoteQuery } from './__generated__/Node-vote';
import { PartyLink } from '../../links'; import { PartyLink } from '../../links';
import { Time } from '../../time'; import { Time } from '../../time';
import { import {

View File

@ -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 (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{u.nodeId ? (
<TableRow modifier="bordered">
<TableCell>{t('Node')}</TableCell>
<TableCell>
<NodeLink id={u.nodeId} />
</TableCell>
</TableRow>
) : null}
{u.amount ? (
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>
<GovernanceAssetBalance price={u.amount} />
</TableCell>
</TableRow>
) : null}
{u.method ? (
<TableRow modifier="bordered">
<TableCell>{t('When')}</TableCell>
<TableCell>{methodText[u.method]}</TableCell>
</TableRow>
) : null}
</TableWithTbody>
);
};

View File

@ -13,6 +13,7 @@ export interface BlockExplorerTransactionResult {
signature: { signature: {
value: string; value: string;
}; };
error?: string;
} }
export interface BlockExplorerTransactions { export interface BlockExplorerTransactions {