feat(explorer): add chain event details pages (#2194)

* feat(explorer): chain event views
This commit is contained in:
Edd 2022-11-23 11:07:45 +00:00 committed by GitHub
parent be39008b42
commit 031cd9258e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 2819 additions and 116 deletions

View File

@ -6,6 +6,7 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_BLOCK_EXPLORER=
# App flags

View File

@ -5,6 +5,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
# App flags
NX_EXPLORER_ASSETS=1

View File

@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io

View File

@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
NX_VEGA_ENV=MAINNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_ETHERSCAN_URL=https://etherscan.io

View File

@ -8,3 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io

View File

@ -10,3 +10,4 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io

View File

@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https:
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_ETHERSCAN_URL=https://be.stagnet3.vega.xyz/rest

View File

@ -9,4 +9,5 @@ NX_VEGA_ENV=TESTNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_NETWORKS={}
NX_VEGA_NETWORKS={}
NX_ETHERSCAN_URL=https://sepolia.etherscan.io

View File

@ -5,3 +5,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io

View File

@ -66,6 +66,14 @@
"passWithNoTests": true
}
},
"generate-types": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.62.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
]
}
},
"build-netlify": {
"executor": "@nrwl/workspace:run-commands",
"options": {

View File

@ -26,7 +26,7 @@ const AssetLink = ({ id, ...props }: AssetLinkProps) => {
}
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
{label}
</Link>
);

View File

@ -0,0 +1,33 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type EthExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
};
export const EthExplorerLink = ({
id,
type,
...props
}: EthExplorerLinkProps) => {
const link = `${DATA_SOURCES.ethExplorerUrl}/${type}/${id}`;
return (
<a
className="underline external"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
{id}
</a>
);
};

View File

@ -19,7 +19,7 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
variables: { id },
});
let label: string = id;
let label = id;
if (data?.market?.tradableInstrument.instrument.name) {
label = data.market.tradableInstrument.instrument.name;

View File

@ -0,0 +1,14 @@
import { t } from '@vegaprotocol/react-helpers';
interface TimeProps {
date: string | null | undefined;
}
export const Time = ({ date }: TimeProps) => {
if (!date) {
return <>{t('Date unknown')}</>;
}
const timeFormatted = new Date(date).toLocaleString();
return <span>{timeFormatted}</span>;
};

View File

@ -0,0 +1,75 @@
import { Schema 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: string, withdrawnTimestamp?: string | 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: string, creditedTimestamp?: string | 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

@ -0,0 +1,139 @@
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
import { TxDetailsChainEventBuiltinDeposit } from './tx-builtin-deposit';
import { TxDetailsChainEventStakeDeposit } from './tx-stake-deposit';
import { TxDetailsChainEventStakeRemove } from './tx-stake-remove';
import { TxDetailsChainEventStakeTotalSupply } from './tx-stake-totalsupply';
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
import { TxDetailsChainEventErc20AssetList } from './tx-erc20-asset-list';
import { TxDetailsChainEventErc20AssetLimitsUpdated } from './tx-erc20-asset-limits-updated';
import { TxDetailsChainEventErc20BridgePause } from './tx-erc20-bridge-pause';
import { TxDetailsChainEventDeposit } from './tx-erc20-deposit';
import isUndefined from 'lodash/isUndefined';
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
interface ChainEventProps {
txData: BlockExplorerTransactionResult | undefined;
}
/**
* Inspects the chain event to determine which details view to show. The list of types
* comes from the Swagger document that Block Explorer provides, which can be viewed at
* https://docs.vega.xyz/testnet/api/rest/explorer/block-explorer-list-transactions
*
* This component should have one entry per chain event type, however if there isn't
* a bespoke view for an event the tx-details-shared will still render some basic
* overview and the transaction viewer will still let people view the raw TX.
*
* Most chain events simply render more table rows for the header table
*
* @returns React.JSXElement
*/
export const ChainEvent = ({ txData }: ChainEventProps) => {
const e = txData?.command.chainEvent;
if (!e) {
return null;
}
// Builtin Asset events
if (e.builtin) {
if (e.builtin.deposit) {
return <TxDetailsChainEventBuiltinDeposit deposit={e.builtin.deposit} />;
}
if (e.builtin?.withdrawal) {
return (
<TxDetailsChainEventBuiltinWithdrawal
withdrawal={e.builtin?.withdrawal}
/>
);
}
}
// ERC20 asset events
if (e.erc20) {
if (e.erc20.deposit) {
return <TxDetailsChainEventDeposit deposit={e.erc20.deposit} />;
}
if (e.erc20.withdrawal) {
return (
<TxDetailsChainEventBuiltinWithdrawal withdrawal={e.erc20.withdrawal} />
);
}
if (e.erc20.assetList) {
return (
<TxDetailsChainEventErc20AssetList assetList={e.erc20.assetList} />
);
}
if (e.erc20.assetLimitsUpdated) {
return (
<TxDetailsChainEventErc20AssetLimitsUpdated
assetLimitsUpdated={e.erc20.assetLimitsUpdated}
/>
);
}
const bridgeStopped = e.erc20.bridgeStopped;
const bridgeResumed = e.erc20.bridgeResumed;
if (!isUndefined(bridgeStopped) || !isUndefined(bridgeResumed)) {
const isPaused = bridgeStopped === false || bridgeResumed === true;
return <TxDetailsChainEventErc20BridgePause isPaused={isPaused} />;
}
}
// ERC20 multisig events
if (e.erc20Multisig) {
if (e.erc20Multisig.thresholdSet) {
return (
<TxDetailsChainMultisigThreshold
thresholdSet={e.erc20Multisig.thresholdSet}
/>
);
}
if (e.erc20Multisig.signerAdded) {
return (
<TxDetailsChainMultisigSigner signer={e.erc20Multisig.signerAdded} />
);
}
if (e.erc20Multisig.signerRemoved) {
return (
<TxDetailsChainMultisigSigner signer={e.erc20Multisig.signerRemoved} />
);
}
}
// Staking events
if (e.stakingEvent) {
if (e.stakingEvent.stakeDeposited) {
return (
<TxDetailsChainEventStakeDeposit
deposit={e.stakingEvent.stakeDeposited}
/>
);
}
if (e.stakingEvent.stakeRemoved) {
return (
<TxDetailsChainEventStakeRemove remove={e.stakingEvent.stakeRemoved} />
);
}
if (e.stakingEvent.totalSupply) {
return (
<TxDetailsChainEventStakeTotalSupply
update={e.stakingEvent.totalSupply}
/>
);
}
}
// If we hit this return, tx-shared-details should give a basic overview
return null;
};

View File

@ -0,0 +1,16 @@
/**
* Returns a reasonably formatted time from unix timestamp of block height
*
* @param date String or null date
* @returns String date in locale time
*/
export function getBlockTime(date?: string) {
if (!date) {
return '-';
}
const timeInSeconds = parseInt(date, 10);
const timeInMs = timeInSeconds * 1000;
return new Date(timeInMs).toLocaleString();
}

View File

@ -0,0 +1,46 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink, PartyLink } from '../../../links';
interface TxDetailsChainEventBuiltinDepositProps {
deposit: components['schemas']['vegaBuiltinAssetDeposit'];
}
/**
* Someone deposited some of a builtin asset. Builtin assets
* have no value outside the Vega chain and should appear only
* on Test networks.
*/
export const TxDetailsChainEventBuiltinDeposit = ({
deposit,
}: TxDetailsChainEventBuiltinDepositProps) => {
if (!deposit) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Built-in asset deposit')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={deposit.partyId || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId || ''} /> ({t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>{deposit.amount}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,47 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink, PartyLink } from '../../../links';
interface TxDetailsChainEventBuiltinDepositProps {
withdrawal: components['schemas']['vegaBuiltinAssetWithdrawal'];
}
/**
* Someone withdrew some of a builtin asset. Builtin assets
* have no value outside the Vega chain and should appear only
* on Test networks, so someone withdrawing it is pretty rare
*/
export const TxDetailsChainEventBuiltinWithdrawal = ({
withdrawal,
}: TxDetailsChainEventBuiltinDepositProps) => {
if (!withdrawal) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Built-in asset withdrawal')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={withdrawal.partyId || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId || ''} /> ({t('built in asset')}
)
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>{withdrawal.amount}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,37 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
interface TxDetailsChainEventErc20AssetDelistProps {
assetDelist: components['schemas']['vegaERC20AssetDelist'];
}
/**
* An ERC20 asset was removed from the bridge,
* The link should link to an asset that doesn't exist. Which feels odd
* but I think is better than having no link - in case something
* weird is up and it still exists
*/
export const TxDetailsChainEventErc20AssetDelist = ({
assetDelist,
}: TxDetailsChainEventErc20AssetDelistProps) => {
if (!assetDelist) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 asset removed')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Removed Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetDelist.vegaAssetId || ''} />
</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,65 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventErc20AssetLimitsUpdatedProps {
assetLimitsUpdated: components['schemas']['vegaERC20AssetLimitsUpdated'];
}
/**
* An ERC20 asset was had its limits changed. The limits are in place
* at first to prevent users depositing large sums while the network
* is still new, as a safety measure.
* - Lifetime limit is how much can be withdrawn of this asset to a
* single ethereum address
* - Withdraw threshold is the size of a withdrawal that will incur
* a delay in processing. The delay is set by a network parameter
*/
export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
assetLimitsUpdated,
}: TxDetailsChainEventErc20AssetLimitsUpdatedProps) => {
if (!assetLimitsUpdated) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 asset limits updated')}</TableCell>
</TableRow>
{assetLimitsUpdated.sourceEthereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('ERC20 asset')}</TableCell>
<TableCell>
<EthExplorerLink
id={assetLimitsUpdated.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetLimitsUpdated.vegaAssetId || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Total lifetime limit')}</TableCell>
<TableCell>{assetLimitsUpdated.lifetimeLimits}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset withdrawal threshold')}</TableCell>
<TableCell>{assetLimitsUpdated.withdrawThreshold}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,51 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventErc20AssetListProps {
assetList: components['schemas']['vegaERC20AssetList'];
}
/**
* An ERC20 asset was proposed and then enacted on the bridge,
* a Asset List event will be emitted that tells the core to
* create the asset. That's this event - it's very basic
*/
export const TxDetailsChainEventErc20AssetList = ({
assetList,
}: TxDetailsChainEventErc20AssetListProps) => {
if (!assetList) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 asset added')}</TableCell>
</TableRow>
{assetList.assetSource ? (
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={assetList.assetSource}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Added Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetList.vegaAssetId || ''} />
</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,24 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
interface TxDetailsChainEventErc20BridgePauseProps {
isPaused: boolean;
}
/**
* The bridge was either paused or unpaused, preventing withdrawals
* or deposits from being enacted. This will only happen if the
* validators have signed a multisig bundle requiring it to happen
*/
export const TxDetailsChainEventErc20BridgePause = ({
isPaused,
}: TxDetailsChainEventErc20BridgePauseProps) => {
const event = isPaused ? 'pause' : 'unpaused';
return (
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t(`ERC20 bridge ${event}`)}</TableCell>
</TableRow>
);
};

View File

@ -0,0 +1,59 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink, PartyLink } from '../../../links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventProps {
deposit: components['schemas']['vegaERC20Deposit'];
}
/**
* Someone deposited some erc20
*/
export const TxDetailsChainEventDeposit = ({
deposit,
}: TxDetailsChainEventProps) => {
if (!deposit) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 deposit')}</TableCell>
</TableRow>
{deposit.sourceEthereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={deposit.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={deposit.targetPartyId || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>{deposit.amount}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,51 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventWithdrawalProps {
withdrawal: components['schemas']['vegaERC20Withdrawal'];
}
/**
* Someone deposited some erc20
*/
export const TxDetailsChainEventWithdrawal = ({
withdrawal,
}: TxDetailsChainEventWithdrawalProps) => {
if (!withdrawal) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 withdrawal')}</TableCell>
</TableRow>
{withdrawal.targetEthereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<EthExplorerLink
id={withdrawal.targetEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId || ''} />
</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,50 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { getBlockTime } from './lib/get-block-time';
interface TxDetailsChainMultisigSignerProps {
signer:
| components['schemas']['vegaERC20SignerAdded']
| components['schemas']['vegaERC20SignerRemoved'];
}
/**
* Someone updated multsig signer set, either removing or adding someone
*/
export const TxDetailsChainMultisigSigner = ({
signer,
}: TxDetailsChainMultisigSignerProps) => {
if (!signer) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const blockTime = getBlockTime(signer.blockTime);
const target =
'newSigner' in signer
? signer.newSigner
: 'oldSigner' in signer
? signer.oldSigner
: '';
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
{'newSigner' in signer
? t('Add ERC20 bridge multisig signer')
: t('Remove ERC20 bridge multsig signer')}
</TableRow>
<TableRow modifier="bordered">
<TableCell>
{'newSigner' in signer ? t('Add signer') : t('Remove signer')}
</TableCell>
<TableCell>{target}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Signer change at')}</TableCell>
<TableCell>{blockTime}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,59 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import isNumber from 'lodash/isNumber';
/**
* Returns a reasonably formatted time from unix timestamp of block height
*
* @param date String or null date
* @returns String date in locale time
*/
function getBlockTime(date?: string) {
if (!date) {
return '-';
}
const timeInSeconds = parseInt(date, 10);
const timeInMs = timeInSeconds * 1000;
return new Date(timeInMs).toLocaleString();
}
interface TxDetailsChainMultisigThresholdProps {
thresholdSet: components['schemas']['vegaERC20MultiSigEvent']['thresholdSet'];
}
/**
* Someone updated multsig threshold value on the smart contract.
* It's a percentage, with 1000 being 100% and 0 being 0%.
*/
export const TxDetailsChainMultisigThreshold = ({
thresholdSet,
}: TxDetailsChainMultisigThresholdProps) => {
if (!thresholdSet) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const blockTime = getBlockTime(thresholdSet.blockTime);
const threshold = isNumber(thresholdSet.newThreshold)
? thresholdSet.newThreshold / 10
: '-';
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('ERC20 multisig threshold set')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Threshold')}</TableCell>
<TableCell>{threshold}%</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Threshold set from')}</TableCell>
<TableCell>{blockTime}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,49 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
interface TxDetailsChainEventStakeDepositProps {
deposit: components['schemas']['vegaStakeDeposited'];
}
/**
* Someone addedd some stake for a particular party
* This should link to the Governance asset, but doesn't
* as that would require checking the Network Paramters
* Ethereum address should also be a link to an ETH block explorer
*/
export const TxDetailsChainEventStakeDeposit = ({
deposit,
}: TxDetailsChainEventStakeDepositProps) => {
if (!deposit) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Stake deposited')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>{deposit.ethereumAddress || ''}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={deposit.vegaPublicKey || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>{deposit.amount}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Deposited at')}</TableCell>
<TableCell>{deposit.amount}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,49 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
interface TxDetailsChainEventStakeRemoveProps {
remove: components['schemas']['vegaStakeRemoved'];
}
/**
* Someone addedd some stake for a particular party
* This should link to the Governance asset, but doesn't
* as that would require checking the Network Parameters
* Ethereum address should also be a link to an ETH block explorer
*/
export const TxDetailsChainEventStakeRemove = ({
remove,
}: TxDetailsChainEventStakeRemoveProps) => {
if (!remove) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Stake removed')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>{remove.ethereumAddress || ''}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={remove.vegaPublicKey || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>{remove.amount}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Deposited at')}</TableCell>
<TableCell>{remove.blockTime}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,54 @@
import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventStakeTotalSupplyProps {
update: components['schemas']['vegaStakeTotalSupply'];
}
/**
* Chain event set the total supply of the governance asset
* Happens whenever the total supply changes, or when the chain
* restarts and the total supply is detected.
*/
export const TxDetailsChainEventStakeTotalSupply = ({
update,
}: TxDetailsChainEventStakeTotalSupplyProps) => {
if (!update) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
let totalSupply = update.totalSupply || '';
if (totalSupply.length > 0) {
totalSupply = formatNumber(toBigNum(totalSupply, 18));
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Stake total supply update')}</TableCell>
</TableRow>
{update.tokenAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={update.tokenAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Total supply')}</TableCell>
<TableCell>{totalSupply}</TableCell>
</TableRow>
</>
);
};

View File

@ -0,0 +1,32 @@
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
}
}
}

View File

@ -1,4 +1,3 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import { BlockLink, PartyLink } from '../../../links/';
@ -6,6 +5,7 @@ import { TimeAgo } from '../../../time-ago';
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
import { Time } from '../../../time';
interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined;
@ -29,11 +29,6 @@ export const TxDetailsShared = ({
const time: string = blockData?.result.block.header.time || '';
const height: string = blockData?.result.block.header.height || '';
let timeFormatted = '';
if (time) {
timeFormatted = new Date(time).toLocaleString();
}
return (
<>
@ -56,7 +51,9 @@ export const TxDetailsShared = ({
<TableCell>
{time ? (
<div>
<span className="mr-5">{timeFormatted} </span>
<span className="mr-5">
<Time date={time} />
</span>
<span>
<TimeAgo date={time} />
</span>

View File

@ -1,9 +1,5 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import type {
BlockExplorerTransactionResult,
BatchMarketInstructions,
} from '../../../routes/types/block-explorer-response';
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 { TableWithTbody, TableRow, TableCell } from '../../table';
@ -26,16 +22,17 @@ export const TxDetailsBatch = ({
pubKey,
blockData,
}: TxDetailsBatchProps) => {
if (!txData) {
if (!txData || !txData.command.batchMarketInstructions) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd = txData.command as BatchMarketInstructions;
const batchSubmissions = cmd.batchMarketInstructions.submissions.length;
const batchAmendments = cmd.batchMarketInstructions.amendments.length;
const batchCancellations = cmd.batchMarketInstructions.cancellations.length;
const batchTotal = batchSubmissions + batchAmendments + batchCancellations;
const countSubmissions =
txData.command.batchMarketInstructions.submissions?.length || 0;
const countAmendments =
txData.command.batchMarketInstructions.amendments?.length || 0;
const countCancellations =
txData.command.batchMarketInstructions.cancellations?.length || 0;
const countTotal = countSubmissions + countAmendments + countCancellations;
return (
<TableWithTbody>
@ -43,7 +40,7 @@ export const TxDetailsBatch = ({
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
<span>{batchTotal}</span>
<span>{countTotal}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@ -51,7 +48,7 @@ export const TxDetailsBatch = ({
<span className="ml-5">{t('Submissions')}</span>
</TableCell>
<TableCell>
<span>{batchSubmissions}</span>
<span>{countSubmissions}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@ -59,7 +56,7 @@ export const TxDetailsBatch = ({
<span className="ml-5">{t('Amendments')}</span>
</TableCell>
<TableCell>
<span>{batchAmendments}</span>
<span>{countAmendments}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@ -67,7 +64,7 @@ export const TxDetailsBatch = ({
<span className="ml-5">{t('Cancellations')}</span>
</TableCell>
<TableCell>
<span>{batchCancellations}</span>
<span>{countCancellations}</span>
</TableCell>
</TableRow>
</TableWithTbody>

View File

@ -1,13 +1,10 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import type {
BlockExplorerTransactionResult,
ChainEvent,
} from '../../../routes/types/block-explorer-response';
import { AssetLink, PartyLink } from '../../links';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { TableWithTbody } from '../../table';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { ChainEvent } from './chain-events';
interface TxDetailsChainEventProps {
txData: BlockExplorerTransactionResult | undefined;
@ -20,10 +17,10 @@ interface TxDetailsChainEventProps {
* Multiple events will relay the same data, from each validator, so that the
* deposit/withdrawal can be verified independently.
*
* Design considerations so far:
* - The ethereum address should be a link to an Ethereum explorer
* - Sender and recipient are shown because they are easy
* - Amount is not shown because there is no formatter by asset component
* There are so many chain events that the specific components have been broken
* out in to individual components. `getChainEventComponent` determines which
* is the most appropriate based on the transaction shape. See that function
* for more information.
*/
export const TxDetailsChainEvent = ({
txData,
@ -33,32 +30,11 @@ export const TxDetailsChainEvent = ({
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd = txData.command as ChainEvent;
const assetId = cmd.chainEvent.erc20.deposit.vegaAssetId;
const sender = cmd.chainEvent.erc20.deposit.sourceEthereumAddress;
const recipient = cmd.chainEvent.erc20.deposit.targetPartyId;
return (
<TableWithTbody>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={assetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Sender')}</TableCell>
<TableCell>
<span>{sender}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={recipient} />
</TableCell>
</TableRow>
<ChainEvent txData={txData} />
</TableWithTbody>
);
};

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { useMemo } from 'react';
import { DATA_SOURCES } from '../../../config';
import { t, useFetch } from '@vegaprotocol/react-helpers';
import { TxDetailsOrder } from './tx-order';
@ -10,11 +10,10 @@ import { TxDetailsGeneric } from './tx-generic';
import { TxDetailsBatch } from './tx-batch';
import { TxDetailsChainEvent } from './tx-chain-event';
import { TxContent } from '../../../routes/txs/id/tx-content';
type resultOrNull = BlockExplorerTransactionResult | undefined;
import { TxDetailsNodeVote } from './tx-node-vote';
interface TxDetailsWrapperProps {
txData: resultOrNull;
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
height: string;
}
@ -61,7 +60,7 @@ export const TxDetailsWrapper = ({
* @param txData
* @returns JSX.Element
*/
function getTransactionComponent(txData: resultOrNull) {
function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
if (!txData) {
return TxDetailsGeneric;
}
@ -77,6 +76,8 @@ function getTransactionComponent(txData: resultOrNull) {
return TxDetailsBatch;
case 'Chain Event':
return TxDetailsChainEvent;
case 'Node Vote':
return TxDetailsNodeVote;
default:
return TxDetailsGeneric;
}

View File

@ -1,4 +1,3 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';

View File

@ -1,9 +1,5 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import type {
BlockExplorerTransactionResult,
ValidatorHeartbeat,
} from '../../../routes/types/block-explorer-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { BlockLink, NodeLink } from '../../links/';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
@ -56,11 +52,12 @@ export const TxDetailsHeartbeat = ({
pubKey,
blockData,
}: TxDetailsHeartbeatProps) => {
if (!txData) {
if (!txData || !txData.command.validatorHeartbeat) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd = txData.command as ValidatorHeartbeat;
const nodeId = txData.command.validatorHeartbeat.nodeId || '';
const blockHeight = txData.command.blockHeight || '';
return (
<TableWithTbody>
@ -68,18 +65,18 @@ export const TxDetailsHeartbeat = ({
<TableRow modifier="bordered">
<TableCell>{t('Node')}</TableCell>
<TableCell>
<NodeLink id={cmd.validatorHeartbeat.nodeId} />
<NodeLink id={nodeId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Signed block height')}</TableCell>
<TableCell>
<BlockLink height={cmd.blockHeight} />
<BlockLink height={blockHeight} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Freshness (lower is better)')}</TableCell>
<TableCell>{scoreFreshness(txData.block, cmd.blockHeight)}</TableCell>
<TableCell>{scoreFreshness(txData.block, blockHeight)}</TableCell>
</TableRow>
</TableWithTbody>
);

View File

@ -1,9 +1,5 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import type {
AmendLiquidityProvisionOrder,
BlockExplorerTransactionResult,
} from '../../../routes/types/block-explorer-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links/';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
@ -29,7 +25,7 @@ export const TxDetailsLPAmend = ({
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd = txData.command as AmendLiquidityProvisionOrder;
const marketId = txData.command.liquidityProvisionAmendment?.marketId || '';
return (
<TableWithTbody>
@ -37,7 +33,7 @@ export const TxDetailsLPAmend = ({
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={cmd.liquidityProvisionAmendment.marketId} />
<MarketLink id={marketId} />
</TableCell>
</TableRow>
</TableWithTbody>

View File

@ -0,0 +1,144 @@
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 type { ExplorerNodeVoteQueryResult } from './__generated___/node-vote';
import { useExplorerNodeVoteQuery } from './__generated___/node-vote';
import { PartyLink } from '../../links';
import { Time } from '../../time';
interface TxDetailsNodeVoteProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* If there is not yet a custom component for a transaction, just display
* the basic details. This allows someone to view the decoded transaction.
*/
export const TxDetailsNodeVote = ({
txData,
pubKey,
blockData,
}: TxDetailsNodeVoteProps) => {
const id = txData?.command.nodeVote?.reference || '';
const { data } = useExplorerNodeVoteQuery({
variables: {
id,
},
// Required as one of these queries will needlessly error
errorPolicy: 'ignore',
});
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<TableWithTbody>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{data && !!data.deposit
? TxDetailsNodeVoteDeposit({ deposit: data })
: data && !!data.withdrawal
? TxDetailsNodeVoteWithdrawal({ withdrawal: data })
: null}
</TableWithTbody>
);
};
interface TxDetailsNodeVoteDepositProps {
deposit: ExplorerNodeVoteQueryResult['data'];
}
export function TxDetailsNodeVoteDeposit({
deposit,
}: TxDetailsNodeVoteDepositProps) {
if (!deposit) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Witnessed Event type')}</TableCell>
{deposit?.deposit?.txHash ? (
<TableCell>{t('ERC20 deposit')}</TableCell>
) : (
<TableCell>{t('Built-in asset deposit')}</TableCell>
)}
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('To party')}:</TableCell>
<TableCell>
<PartyLink id={deposit?.deposit?.party?.id || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Credited on')}:</TableCell>
<TableCell>
<Time date={deposit?.deposit?.creditedTimestamp} />
</TableCell>
{deposit?.deposit?.txHash ? (
<TxHash hash={deposit?.deposit?.txHash} />
) : null}
</TableRow>
</>
);
}
interface TxDetailsNodeVoteWithdrawalProps {
withdrawal: ExplorerNodeVoteQueryResult['data'];
}
export function TxDetailsNodeVoteWithdrawal({
withdrawal,
}: TxDetailsNodeVoteWithdrawalProps) {
if (!withdrawal) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Witnessed Event type')}</TableCell>
{withdrawal?.withdrawal?.txHash ? (
<TableCell>{t('ERC20 withdrawal')}</TableCell>
) : (
<TableCell>{t('Built-in asset withdrawal')}</TableCell>
)}
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('From party')}:</TableCell>
<TableCell>
<PartyLink id={withdrawal?.deposit?.party?.id || ''} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Withdrawn on')}:</TableCell>
<TableCell>
<Time date={withdrawal?.withdrawal?.withdrawnTimestamp} />
</TableCell>
{withdrawal?.withdrawal?.txHash ? (
<TxHash hash={withdrawal?.withdrawal?.txHash} />
) : null}
</TableRow>
</>
);
}
interface TxDetailsEthTxHashProps {
hash: string;
}
export function TxHash({ hash }: TxDetailsEthTxHashProps) {
if (!hash) {
return null;
}
return (
<TableRow modifier="bordered">
<TableCell>Ethereum TX:</TableCell>
<TableCell>{hash}</TableCell>
</TableRow>
);
}

View File

@ -1,9 +1,5 @@
import React from 'react';
import { t } from '@vegaprotocol/react-helpers';
import type {
BlockExplorerTransactionResult,
SubmitOrder,
} from '../../../routes/types/block-explorer-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links/';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
@ -26,11 +22,10 @@ export const TxDetailsOrder = ({
pubKey,
blockData,
}: TxDetailsOrderProps) => {
if (!txData) {
if (!txData || !txData.command.orderSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const cmd = txData.command as SubmitOrder;
const marketId = txData.command.orderSubmission.marketId || '-';
return (
<TableWithTbody>
@ -38,7 +33,7 @@ export const TxDetailsOrder = ({
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={cmd.orderSubmission.marketId} />
<MarketLink id={marketId} />
</TableCell>
</TableRow>
</TableWithTbody>

View File

@ -1,5 +1,9 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../types/explorer';
interface TxOrderTypeProps {
orderType: string;
chainEvent?: components['schemas']['v1ChainEvent'];
className?: string;
}
@ -32,13 +36,73 @@ const displayString: StringMap = {
ValidatorHeartbeat: 'Validator Heartbeat',
};
export const TxOrderType = ({ orderType }: TxOrderTypeProps) => {
/**
* Given a chain event, will try to provide a more useful label
* @param chainEvent
* @returns
*/
export function getLabelForChainEvent(
chainEvent: components['schemas']['v1ChainEvent']
): string {
if (chainEvent.builtin) {
if (chainEvent.builtin.deposit) {
return t('Built-in deposit');
} else if (chainEvent.builtin.withdrawal) {
return t('Built-in withdraw');
}
return t('Built-in event');
} else if (chainEvent.erc20) {
if (chainEvent.erc20.assetDelist) {
return t('ERC20 delist');
} else if (chainEvent.erc20.assetList) {
return t('ERC20 list');
} else if (chainEvent.erc20.bridgeResumed) {
return t('Bridge resume');
} else if (chainEvent.erc20.bridgeStopped) {
return t('Bridge pause');
} else if (chainEvent.erc20.deposit) {
return t('ERC20 deposit');
} else if (chainEvent.erc20.withdrawal) {
return t('ERC20 withdraw');
}
return t('ERC20 event');
} else if (chainEvent.stakingEvent) {
if (chainEvent.stakingEvent.stakeDeposited) {
return t('Stake add');
} else if (chainEvent.stakingEvent.stakeRemoved) {
return t('Stake remove');
}
return t('Staking event');
} else if (chainEvent.erc20Multisig) {
if (chainEvent.erc20Multisig.signerAdded) {
return t('Signer adde');
} else if (chainEvent.erc20Multisig.signerRemoved) {
return t('Signer remove');
} else if (chainEvent.erc20Multisig.thresholdSet) {
return t('Signer threshold');
}
return t('Multisig update');
}
return t('Chain Event');
}
export const TxOrderType = ({ orderType, chainEvent }: TxOrderTypeProps) => {
let type = displayString[orderType] || orderType;
let colours = 'text-white dark:text-white bg-zinc-800 dark:bg-zinc-800';
// This will get unwieldy and should probably produce a different colour of tag
if (type === 'Chain Event' && !!chainEvent) {
type = getLabelForChainEvent(chainEvent);
colours =
'text-white dark-text-white bg-vega-pink-dark dark:bg-vega-pink-dark';
}
return (
<div
data-testid="tx-type"
className="text-sm rounded-md leading-none px-2 py-2 inline-block text-white dark:text-white bg-zinc-800 dark:bg-zinc-800 "
className={`text-sm rounded-md leading-none px-2 py-2 inline-block ${colours}`}
>
{displayString[orderType] || orderType}
{type}
</div>
);
};

View File

@ -13,6 +13,7 @@ export const TxsInfiniteListItem = ({
type,
block,
index,
command,
}: Partial<BlockExplorerTransactionResult>) => {
if (
!hash ||
@ -54,7 +55,7 @@ export const TxsInfiniteListItem = ({
/>
</div>
<div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
<TxOrderType orderType={type} />
<TxOrderType orderType={type} chainEvent={command?.chainEvent} />
</div>
<div
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"

View File

@ -26,7 +26,7 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
expiresAt: '1664966445481288736',
type: 'TYPE_LIMIT',
reference: 'traderbot',
peggedOrder: null,
peggedOrder: undefined,
},
},
}));

View File

@ -93,7 +93,7 @@ export const TxsInfiniteList = ({
{({ onItemsRendered, ref }) => (
<List
className="List"
height={595}
height={995}
itemCount={itemCount}
itemSize={isStacked ? 134 : 72}
onItemsRendered={onItemsRendered}

View File

@ -42,7 +42,9 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
</TableRow>
</thead>
<tbody>
{decodedBlockData.map(({ TxHash, PubKey, Type }) => {
{decodedBlockData.map(({ TxHash, PubKey, Type, Command }) => {
const chainEvent = JSON.parse(Command || '');
return (
<TableRow
modifier="bordered"
@ -66,7 +68,7 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
/>
</TableCell>
<TableCell modifier="bordered">
<TxOrderType orderType={Type} />
<TxOrderType orderType={Type} chainEvent={chainEvent} />
</TableCell>
</TableRow>
);

View File

@ -16,6 +16,7 @@ export const ENV = {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
},
flags: {
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),

View File

@ -1,3 +1,4 @@
import type { components } from '../../../types/explorer';
import type { UnknownObject } from '../../components/nested-data-list';
export interface BlockExplorerTransactionResult {
@ -8,13 +9,7 @@ export interface BlockExplorerTransactionResult {
type: string;
code: number;
cursor: string;
command:
| ValidatorHeartbeat
| SubmitOrder
| StateVariableProposal
| AmendLiquidityProvisionOrder
| BatchMarketInstructions
| ChainEvent;
command: components['schemas']['v1InputData'];
}
export interface BlockExplorerTransactions {
@ -103,16 +98,23 @@ export interface BatchCancellationInstruction {
export interface ChainEvent {
blockHeight: string;
nonce: string;
chainEvent: {
erc20: {
deposit: ERC20Deposit;
};
chainEvent: components['schemas']['v1ChainEvent'];
}
export interface ChainEventErc20Multisig {
erc20Multisig: {
block: string;
index: string;
};
}
export interface ERC20Deposit {
vegaAssetId: string;
sourceEthereumAddress: string;
targetPartyId: string;
amount: string;
export interface ChainEventErc20Deposit {
erc20: {
deposit: {
vegaAssetId: string;
sourceEthereumAddress: string;
targetPartyId: string;
amount: string;
};
};
}

1563
apps/explorer/src/types/explorer.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
"noFallthroughCasesInSwitch": true,
"lib": ["es5", "es6", "dom", "dom.iterable"]
},
"exclude": ["./src/types/explorer.d.ts"],
"include": [],
"references": [
{