explorer: Add block rewards to block details page (#14786)
* explorer: add block rewards * add key to tr map
This commit is contained in:
65
explorer/src/components/block/BlockHistoryCard.tsx
Normal file
65
explorer/src/components/block/BlockHistoryCard.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { ConfirmedBlock } from "@solana/web3.js";
|
||||||
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
|
import { Signature } from "components/common/Signature";
|
||||||
|
import bs58 from "bs58";
|
||||||
|
|
||||||
|
export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
|
||||||
|
if (block.transactions.length === 0) {
|
||||||
|
return <ErrorCard text="This block has no transactions" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header align-items-center">
|
||||||
|
<h3 className="card-header-title">Block Transactions</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="table-responsive mb-0">
|
||||||
|
<table className="table table-sm table-nowrap card-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-muted">Result</th>
|
||||||
|
<th className="text-muted">Transaction Signature</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="list">
|
||||||
|
{block.transactions.map((tx, i) => {
|
||||||
|
let statusText;
|
||||||
|
let statusClass;
|
||||||
|
let signature: React.ReactNode;
|
||||||
|
if (tx.meta?.err || !tx.transaction.signature) {
|
||||||
|
statusClass = "warning";
|
||||||
|
statusText = "Failed";
|
||||||
|
} else {
|
||||||
|
statusClass = "success";
|
||||||
|
statusText = "Success";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx.transaction.signature) {
|
||||||
|
signature = (
|
||||||
|
<Signature
|
||||||
|
signature={bs58.encode(tx.transaction.signature)}
|
||||||
|
link
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>
|
||||||
|
<span className={`badge badge-soft-${statusClass}`}>
|
||||||
|
{statusText}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{signature}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
import bs58 from "bs58";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { TableCardBody } from "components/common/TableCardBody";
|
import { TableCardBody } from "components/common/TableCardBody";
|
||||||
import { useBlock, useFetchBlock, FetchStatus } from "providers/block";
|
import { useBlock, useFetchBlock, FetchStatus } from "providers/block";
|
||||||
import { Signature } from "components/common/Signature";
|
|
||||||
import { ErrorCard } from "components/common/ErrorCard";
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
import { LoadingCard } from "components/common/LoadingCard";
|
import { LoadingCard } from "components/common/LoadingCard";
|
||||||
import { Slot } from "components/common/Slot";
|
import { Slot } from "components/common/Slot";
|
||||||
import { ClusterStatus, useCluster } from "providers/cluster";
|
import { ClusterStatus, useCluster } from "providers/cluster";
|
||||||
|
import { BlockHistoryCard } from "./BlockHistoryCard";
|
||||||
|
import { BlockRewardsCard } from "./BlockRewardsCard";
|
||||||
|
|
||||||
export function BlockHistoryCard({ slot }: { slot: number }) {
|
export function BlockOverviewCard({ slot }: { slot: number }) {
|
||||||
const confirmedBlock = useBlock(slot);
|
const confirmedBlock = useBlock(slot);
|
||||||
const fetchBlock = useFetchBlock();
|
const fetchBlock = useFetchBlock();
|
||||||
const { status } = useCluster();
|
const { status } = useCluster();
|
||||||
@ -67,61 +67,8 @@ export function BlockHistoryCard({ slot }: { slot: number }) {
|
|||||||
</TableCardBody>
|
</TableCardBody>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{block.transactions.length === 0 ? (
|
<BlockRewardsCard block={block} />
|
||||||
<ErrorCard text="This block has no transactions" />
|
<BlockHistoryCard block={block} />
|
||||||
) : (
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header align-items-center">
|
|
||||||
<h3 className="card-header-title">Block Transactions</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="table-responsive mb-0">
|
|
||||||
<table className="table table-sm table-nowrap card-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="text-muted">Result</th>
|
|
||||||
<th className="text-muted">Transaction Signature</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="list">
|
|
||||||
{block.transactions.map((tx, i) => {
|
|
||||||
let statusText;
|
|
||||||
let statusClass;
|
|
||||||
let signature: React.ReactNode;
|
|
||||||
if (tx.meta?.err || !tx.transaction.signature) {
|
|
||||||
statusClass = "warning";
|
|
||||||
statusText = "Failed";
|
|
||||||
} else {
|
|
||||||
statusClass = "success";
|
|
||||||
statusText = "Success";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.transaction.signature) {
|
|
||||||
signature = (
|
|
||||||
<Signature
|
|
||||||
signature={bs58.encode(tx.transaction.signature)}
|
|
||||||
link
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={i}>
|
|
||||||
<td>
|
|
||||||
<span className={`badge badge-soft-${statusClass}`}>
|
|
||||||
{statusText}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{signature}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
59
explorer/src/components/block/BlockRewardsCard.tsx
Normal file
59
explorer/src/components/block/BlockRewardsCard.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { lamportsToSolString } from "utils";
|
||||||
|
import { ConfirmedBlock, PublicKey } from "@solana/web3.js";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
|
||||||
|
export function BlockRewardsCard({ block }: { block: ConfirmedBlock }) {
|
||||||
|
if (block.rewards.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header align-items-center">
|
||||||
|
<h3 className="card-header-title">Block Rewards</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="table-responsive mb-0">
|
||||||
|
<table className="table table-sm table-nowrap card-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-muted">Address</th>
|
||||||
|
<th className="text-muted">Type</th>
|
||||||
|
<th className="text-muted">Amount</th>
|
||||||
|
<th className="text-muted">New Balance</th>
|
||||||
|
<th className="text-muted">Percent Change</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{block.rewards.map((reward) => {
|
||||||
|
let percentChange;
|
||||||
|
if (reward.postBalance !== null && reward.postBalance !== 0) {
|
||||||
|
percentChange = (
|
||||||
|
(Math.abs(reward.lamports) /
|
||||||
|
(reward.postBalance - reward.lamports)) *
|
||||||
|
100
|
||||||
|
).toFixed(9);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<tr key={reward.pubkey + reward.rewardType}>
|
||||||
|
<td>
|
||||||
|
<Address pubkey={new PublicKey(reward.pubkey)} link />
|
||||||
|
</td>
|
||||||
|
<td>{reward.rewardType}</td>
|
||||||
|
<td>{lamportsToSolString(reward.lamports)}</td>
|
||||||
|
<td>
|
||||||
|
{reward.postBalance
|
||||||
|
? lamportsToSolString(reward.postBalance)
|
||||||
|
: "-"}
|
||||||
|
</td>
|
||||||
|
<td>{percentChange ? percentChange + "%" : "-"}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BlockHistoryCard } from "components/account/BlockHistoryCard";
|
|
||||||
import { ErrorCard } from "components/common/ErrorCard";
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
|
import { BlockOverviewCard } from "components/block/BlockOverviewCard";
|
||||||
|
|
||||||
type Props = { slot: string };
|
type Props = { slot: string };
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ export function BlockDetailsPage({ slot }: Props) {
|
|||||||
let output = <ErrorCard text={`Block ${slot} is not valid`} />;
|
let output = <ErrorCard text={`Block ${slot} is not valid`} />;
|
||||||
|
|
||||||
if (!isNaN(Number(slot))) {
|
if (!isNaN(Number(slot))) {
|
||||||
output = <BlockHistoryCard slot={Number(slot)} />;
|
output = <BlockOverviewCard slot={Number(slot)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user