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:
parent
71399105ee
commit
1180a5ff97
@ -0,0 +1,5 @@
|
|||||||
|
query ExplorerGovernanceAsset {
|
||||||
|
networkParameter(key: "reward.asset") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
45
apps/explorer/src/app/components/asset-balance/__generated__/Governance-asset.ts
generated
Normal file
45
apps/explorer/src/app/components/asset-balance/__generated__/Governance-asset.ts
generated
Normal 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>;
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
@ -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 },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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>;
|
|
@ -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">— {error}</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -13,6 +13,7 @@ export interface BlockExplorerTransactionResult {
|
|||||||
signature: {
|
signature: {
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockExplorerTransactions {
|
export interface BlockExplorerTransactions {
|
||||||
|
Loading…
Reference in New Issue
Block a user