diff --git a/explorer/src/components/instruction/vote/VoteDetailsCard.tsx b/explorer/src/components/instruction/vote/VoteDetailsCard.tsx index 3d30e5e5ea..c4bdf2d8e0 100644 --- a/explorer/src/components/instruction/vote/VoteDetailsCard.tsx +++ b/explorer/src/components/instruction/vote/VoteDetailsCard.tsx @@ -1,91 +1,127 @@ import React from "react"; -import { ParsedInstruction, SignatureResult } from "@solana/web3.js"; -import { coerce } from "superstruct"; +import { PublicKey } from "@solana/web3.js"; +import { coerce, Struct } from "superstruct"; import { ParsedInfo } from "validators"; -import { VoteInfo } from "./types"; +import { + UpdateCommissionInfo, + UpdateValidatorInfo, + VoteInfo, + VoteSwitchInfo, + WithdrawInfo, + AuthorizeInfo, +} from "./types"; import { InstructionCard } from "../InstructionCard"; import { Address } from "components/common/Address"; import { displayTimestamp } from "utils/date"; +import { UnknownDetailsCard } from "../UnknownDetailsCard"; +import { InstructionDetailsProps } from "components/transaction/InstructionsSection"; +import { camelToTitleCase } from "utils"; +import { useCluster } from "providers/cluster"; +import { reportError } from "utils/sentry"; -export function VoteDetailsCard(props: { - ix: ParsedInstruction; - index: number; - result: SignatureResult; - innerCards?: JSX.Element[]; - childIndex?: number; -}) { - const { ix, index, result, innerCards, childIndex } = props; - const parsed = coerce(props.ix.parsed, ParsedInfo); - const info = coerce(parsed.info, VoteInfo); +export function VoteDetailsCard(props: InstructionDetailsProps) { + const { url } = useCluster(); + + try { + const parsed = coerce(props.ix.parsed, ParsedInfo); + + switch (parsed.type) { + case "vote": + return renderDetails(props, parsed, VoteInfo); + case "authorize": + return renderDetails(props, parsed, AuthorizeInfo); + case "withdraw": + return renderDetails(props, parsed, WithdrawInfo); + case "updateValidator": + return renderDetails( + props, + parsed, + UpdateValidatorInfo + ); + case "updateCommission": + return renderDetails( + props, + parsed, + UpdateCommissionInfo + ); + case "voteSwitch": + return renderDetails(props, parsed, VoteSwitchInfo); + } + } catch (error) { + reportError(error, { + url, + }); + } + + return ; +} + +function renderDetails( + props: InstructionDetailsProps, + parsed: ParsedInfo, + struct: Struct +) { + const info = coerce(parsed.info, struct); + const attributes: JSX.Element[] = []; + + for (let [key, value] of Object.entries(info)) { + if (value instanceof PublicKey) { + value =
; + } + + if (key === "vote") { + attributes.push( + + Vote Hash + +
{value.hash}
+ + + ); + + if (value.timestamp) { + attributes.push( + + Timestamp + + {displayTimestamp(value.timestamp * 1000)} + + + ); + } + + attributes.push( + + Slots + +
+              {value.slots.join("\n")}
+            
+ + + ); + } else { + attributes.push( + + {camelToTitleCase(key)} + {value} + + ); + } + } return ( Program -
- - - - - Vote Account - -
- - - - - Vote Authority - -
- - - - - Clock Sysvar - -
- - - - - Slot Hashes Sysvar - -
- - - - - Vote Hash - -
{info.vote.hash}
- - - - {info.vote.timestamp && ( - - Timestamp - - {displayTimestamp(info.vote.timestamp * 1000)} - - - )} - - - Slots - -
-            {info.vote.slots.join("\n")}
-          
+
+ {attributes} ); } diff --git a/explorer/src/components/instruction/vote/types.ts b/explorer/src/components/instruction/vote/types.ts index ae8a23021f..42de4e47bf 100644 --- a/explorer/src/components/instruction/vote/types.ts +++ b/explorer/src/components/instruction/vote/types.ts @@ -3,6 +3,26 @@ import { array, number, optional, pick, string, StructType } from "superstruct"; import { Pubkey } from "validators/pubkey"; +export type InitializeInfo = StructType; +export const InitializeInfo = pick({ + voteAccount: Pubkey, + rentSysvar: Pubkey, + clockSysvar: Pubkey, + node: Pubkey, + authorizedVoter: Pubkey, + authorizedWithdrawer: Pubkey, + commission: number(), +}); + +export type AuthorizeInfo = StructType; +export const AuthorizeInfo = pick({ + voteAccount: Pubkey, + clockSysvar: Pubkey, + authority: Pubkey, + newAuthority: Pubkey, + authorityType: number(), +}); + export type VoteInfo = StructType; export const VoteInfo = pick({ clockSysvar: Pubkey, @@ -15,3 +35,39 @@ export const VoteInfo = pick({ timestamp: optional(number()), }), }); + +export type WithdrawInfo = StructType; +export const WithdrawInfo = pick({ + voteAccount: Pubkey, + destination: Pubkey, + withdrawAuthority: Pubkey, + lamports: number(), +}); + +export type UpdateValidatorInfo = StructType; +export const UpdateValidatorInfo = pick({ + voteAccount: Pubkey, + newValidatorIdentity: Pubkey, + withdrawAuthority: Pubkey, +}); + +export type UpdateCommissionInfo = StructType; +export const UpdateCommissionInfo = pick({ + voteAccount: Pubkey, + withdrawAuthority: Pubkey, + commission: number(), +}); + +export type VoteSwitchInfo = StructType; +export const VoteSwitchInfo = pick({ + voteAccount: Pubkey, + slotHashesSysvar: Pubkey, + clockSysvar: Pubkey, + voteAuthority: Pubkey, + vote: pick({ + hash: string(), + slots: array(number()), + timestamp: number(), + }), + hash: string(), +}); diff --git a/explorer/src/components/transaction/InstructionsSection.tsx b/explorer/src/components/transaction/InstructionsSection.tsx index 6680f53622..871e8228ed 100644 --- a/explorer/src/components/transaction/InstructionsSection.tsx +++ b/explorer/src/components/transaction/InstructionsSection.tsx @@ -34,8 +34,17 @@ import { useTransactionStatus, } from "providers/transactions"; import { Cluster, useCluster } from "providers/cluster"; -// import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard"; import { UpgradeableBpfLoaderDetailsCard } from "components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard"; +import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard"; + +export type InstructionDetailsProps = { + tx: ParsedTransaction; + ix: ParsedInstruction; + index: number; + result: SignatureResult; + innerCards?: JSX.Element[]; + childIndex?: number; +}; export function InstructionsSection({ signature }: SignatureProps) { const status = useTransactionStatus(signature); @@ -171,8 +180,8 @@ function renderInstructionCard({ return ; case "spl-memo": return ; - /*case "vote": - return ;*/ + case "vote": + return ; default: return ; }