explorer: Add support for all parsed accounts (#11842)
* introduce vote and nonce validators * introduce config, nonce, sysvar, vote validators / types * change ConfigProgram to ConfigProgramData * introduce vote account section and nonce account section, clean up superstructs * nonce section * round out vote account and nonce account * refactor account components, add votes tab * update program data name to program * introduce slot hashes, stake history * introduce blockhashes card and config account * run fix format * remove comment * introduce config section and typings * refactor tabs if blocks * change superstructs to pick in some cases * remove account owners, rename vote history, some nit fixes * general cleanup and improvements * add recency column * add balance row to parsed accounts * union account types under sysvar and config for improved typing. modify row headers for consistency. * remove random spaces * use proper type checking and clean up a cast
This commit is contained in:
parent
f1bbe1cd84
commit
86ca85d72b
|
@ -0,0 +1,61 @@
|
|||
import React from "react";
|
||||
import {
|
||||
RecentBlockhashesInfo,
|
||||
RecentBlockhashesEntry,
|
||||
} from "validators/accounts/sysvar";
|
||||
|
||||
export function BlockhashesCard({
|
||||
blockhashes,
|
||||
}: {
|
||||
blockhashes: RecentBlockhashesInfo;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h3 className="card-header-title">Blockhashes</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-1 text-muted">Recency</th>
|
||||
<th className="w-1 text-muted">Blockhash</th>
|
||||
<th className="text-muted">Fee Calculator</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">
|
||||
{blockhashes.length > 0 &&
|
||||
blockhashes.map((entry: RecentBlockhashesEntry, index) => {
|
||||
return renderAccountRow(entry, index);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
<div className="text-muted text-center">
|
||||
{blockhashes.length > 0 ? "" : "No blockhashes found"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const renderAccountRow = (entry: RecentBlockhashesEntry, index: number) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="w-1">{index + 1}</td>
|
||||
<td className="w-1 text-monospace">{entry.blockhash}</td>
|
||||
<td className="">
|
||||
{entry.feeCalculator.lamportsPerSignature} lamports per signature
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,158 @@
|
|||
import React from "react";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import {
|
||||
ConfigAccount,
|
||||
StakeConfigInfoAccount,
|
||||
ValidatorInfoAccount,
|
||||
} from "validators/accounts/config";
|
||||
import {
|
||||
AccountAddressRow,
|
||||
AccountBalanceRow,
|
||||
AccountHeader,
|
||||
} from "components/common/Account";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
|
||||
const MAX_SLASH_PENALTY = Math.pow(2, 8);
|
||||
|
||||
export function ConfigAccountSection({
|
||||
account,
|
||||
configAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
configAccount: ConfigAccount;
|
||||
}) {
|
||||
switch (configAccount.type) {
|
||||
case "stakeConfig":
|
||||
return (
|
||||
<StakeConfigCard account={account} configAccount={configAccount} />
|
||||
);
|
||||
case "validatorInfo":
|
||||
return (
|
||||
<ValidatorInfoCard account={account} configAccount={configAccount} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function StakeConfigCard({
|
||||
account,
|
||||
configAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
configAccount: StakeConfigInfoAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
|
||||
const warmupCooldownFormatted = new Intl.NumberFormat("en-US", {
|
||||
style: "percent",
|
||||
maximumFractionDigits: 2,
|
||||
}).format(configAccount.info.warmupCooldownRate);
|
||||
|
||||
const slashPenaltyFormatted = new Intl.NumberFormat("en-US", {
|
||||
style: "percent",
|
||||
maximumFractionDigits: 2,
|
||||
}).format(configAccount.info.slashPenalty / MAX_SLASH_PENALTY);
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Stake Config"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Warmup / Cooldown Rate</td>
|
||||
<td className="text-lg-right">{warmupCooldownFormatted}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Slash Penalty</td>
|
||||
<td className="text-lg-right">{slashPenaltyFormatted}</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ValidatorInfoCard({
|
||||
account,
|
||||
configAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
configAccount: ValidatorInfoAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Validator Info"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
{configAccount.info.configData.name && (
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td className="text-lg-right">
|
||||
{configAccount.info.configData.name}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{configAccount.info.configData.keybaseUsername && (
|
||||
<tr>
|
||||
<td>Keybase Username</td>
|
||||
<td className="text-lg-right">
|
||||
{configAccount.info.configData.keybaseUsername}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{configAccount.info.configData.website && (
|
||||
<tr>
|
||||
<td>Website</td>
|
||||
<td className="text-lg-right">
|
||||
<a
|
||||
href={configAccount.info.configData.website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{configAccount.info.configData.website}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{configAccount.info.configData.details && (
|
||||
<tr>
|
||||
<td>Details</td>
|
||||
<td className="text-lg-right">
|
||||
{configAccount.info.configData.details}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{configAccount.info.keys && configAccount.info.keys.length > 1 && (
|
||||
<tr>
|
||||
<td>Signer</td>
|
||||
<td className="text-lg-right">
|
||||
<Address
|
||||
pubkey={new PublicKey(configAccount.info.keys[1].pubkey)}
|
||||
link
|
||||
alignRight
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import React from "react";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { Address } from "components/common/Address";
|
||||
import { NonceAccount } from "validators/accounts/nonce";
|
||||
import {
|
||||
AccountHeader,
|
||||
AccountAddressRow,
|
||||
AccountBalanceRow,
|
||||
} from "components/common/Account";
|
||||
|
||||
export function NonceAccountSection({
|
||||
account,
|
||||
nonceAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
nonceAccount: NonceAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Nonce Account"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Authority</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={nonceAccount.info.authority} alignRight raw link />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Blockhash</td>
|
||||
<td className="text-lg-right">
|
||||
<code>{nonceAccount.info.blockhash}</code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Fee</td>
|
||||
<td className="text-lg-right">
|
||||
{nonceAccount.info.feeCalculator.lamportsPerSignature} lamports per
|
||||
signature
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import React from "react";
|
||||
import {
|
||||
SysvarAccount,
|
||||
SlotHashesInfo,
|
||||
SlotHashEntry,
|
||||
} from "validators/accounts/sysvar";
|
||||
|
||||
export function SlotHashesCard({
|
||||
sysvarAccount,
|
||||
}: {
|
||||
sysvarAccount: SysvarAccount;
|
||||
}) {
|
||||
const slotHashes = sysvarAccount.info as SlotHashesInfo;
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h3 className="card-header-title">Slot Hashes</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-1 text-muted">Slot</th>
|
||||
<th className="text-muted">Blockhash</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">
|
||||
{slotHashes.length > 0 &&
|
||||
slotHashes.map((entry: SlotHashEntry, index) => {
|
||||
return renderAccountRow(entry, index);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
<div className="text-muted text-center">
|
||||
{slotHashes.length > 0 ? "" : "No hashes found"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderAccountRow = (entry: SlotHashEntry, index: number) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="w-1 text-monospace">
|
||||
{entry.slot.toLocaleString("en-US")}
|
||||
</td>
|
||||
<td className="text-monospace">{entry.hash}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
import React from "react";
|
||||
import { lamportsToSolString } from "utils";
|
||||
import {
|
||||
SysvarAccount,
|
||||
StakeHistoryInfo,
|
||||
StakeHistoryEntry,
|
||||
} from "validators/accounts/sysvar";
|
||||
|
||||
export function StakeHistoryCard({
|
||||
sysvarAccount,
|
||||
}: {
|
||||
sysvarAccount: SysvarAccount;
|
||||
}) {
|
||||
const stakeHistory = sysvarAccount.info as StakeHistoryInfo;
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h3 className="card-header-title">Stake History</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-1 text-muted">Epoch</th>
|
||||
<th className="text-muted">Effective (SOL)</th>
|
||||
<th className="text-muted">Activating (SOL)</th>
|
||||
<th className="text-muted">Deactivating (SOL)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">
|
||||
{stakeHistory.length > 0 &&
|
||||
stakeHistory.map((entry: StakeHistoryEntry, index) => {
|
||||
return renderAccountRow(entry, index);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
<div className="text-muted text-center">
|
||||
{stakeHistory.length > 0 ? "" : "No stake history found"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const renderAccountRow = (entry: StakeHistoryEntry, index: number) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="w-1 text-monospace">{entry.epoch}</td>
|
||||
<td className="text-monospace">
|
||||
{lamportsToSolString(entry.stakeHistory.effective)}
|
||||
</td>
|
||||
<td className="text-monospace">
|
||||
{lamportsToSolString(entry.stakeHistory.activating)}
|
||||
</td>
|
||||
<td className="text-monospace">
|
||||
{lamportsToSolString(entry.stakeHistory.deactivating)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,416 @@
|
|||
import React from "react";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import {
|
||||
SysvarAccount,
|
||||
SysvarClockAccount,
|
||||
SysvarEpochScheduleAccount,
|
||||
SysvarFeesAccount,
|
||||
SysvarRecentBlockhashesAccount,
|
||||
SysvarRentAccount,
|
||||
SysvarRewardsAccount,
|
||||
SysvarSlotHashesAccount,
|
||||
SysvarSlotHistoryAccount,
|
||||
SysvarStakeHistoryAccount,
|
||||
} from "validators/accounts/sysvar";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import {
|
||||
AccountHeader,
|
||||
AccountAddressRow,
|
||||
AccountBalanceRow,
|
||||
} from "components/common/Account";
|
||||
import { displayTimestamp } from "utils/date";
|
||||
|
||||
export function SysvarAccountSection({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarAccount;
|
||||
}) {
|
||||
switch (sysvarAccount.type) {
|
||||
case "clock":
|
||||
return (
|
||||
<SysvarAccountClockCard
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "rent":
|
||||
return (
|
||||
<SysvarAccountRentCard
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "rewards":
|
||||
return (
|
||||
<SysvarAccountRewardsCard
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "epochSchedule":
|
||||
return (
|
||||
<SysvarAccountEpochScheduleCard
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "fees":
|
||||
return (
|
||||
<SysvarAccountFeesCard
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "recentBlockhashes":
|
||||
return (
|
||||
<SysvarAccountRecentBlockhashesCard
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "slotHashes":
|
||||
return (
|
||||
<SysvarAccountSlotHashes
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "slotHistory":
|
||||
return (
|
||||
<SysvarAccountSlotHistory
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
case "stakeHistory":
|
||||
return (
|
||||
<SysvarAccountStakeHistory
|
||||
account={account}
|
||||
sysvarAccount={sysvarAccount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function SysvarAccountRecentBlockhashesCard({
|
||||
account,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarRecentBlockhashesAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Recent Blockhashes"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountSlotHashes({
|
||||
account,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarSlotHashesAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Slot Hashes"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountSlotHistory({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarSlotHistoryAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
const history = Array.from(
|
||||
{
|
||||
length: 100,
|
||||
},
|
||||
(v, k) => sysvarAccount.info.nextSlot - k
|
||||
);
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Slot History"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td className="align-top">
|
||||
Slot History{" "}
|
||||
<span className="text-muted">(previous 100 slots)</span>
|
||||
</td>
|
||||
<td className="text-lg-right text-monospace">
|
||||
{history.map((val) => (
|
||||
<p key={val} className="mb-0">
|
||||
{val}
|
||||
</p>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountStakeHistory({
|
||||
account,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarStakeHistoryAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Stake History"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountFeesCard({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarFeesAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Fees"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Lamports Per Signature</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.feeCalculator.lamportsPerSignature}
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountEpochScheduleCard({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarEpochScheduleAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Epoch Schedule"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Slots Per Epoch</td>
|
||||
<td className="text-lg-right">{sysvarAccount.info.slotsPerEpoch}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Leader Schedule Slot Offset</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.leaderScheduleSlotOffset}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Epoch Warmup Enabled</td>
|
||||
<td className="text-lg-right">
|
||||
<code>{sysvarAccount.info.warmup ? "true" : "false"}</code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>First Normal Epoch</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.firstNormalEpoch}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>First Normal Slot</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.firstNormalSlot}
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountClockCard({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarClockAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Clock"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Timestamp</td>
|
||||
<td className="text-lg-right">
|
||||
{displayTimestamp(sysvarAccount.info.unixTimestamp * 1000)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Epoch</td>
|
||||
<td className="text-lg-right">{sysvarAccount.info.epoch}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Leader Schedule Epoch</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.leaderScheduleEpoch}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Slot</td>
|
||||
<td className="text-lg-right">{sysvarAccount.info.slot}</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountRentCard({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarRentAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Rent"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Burn Percent</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.burnPercent + "%"}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Exemption Threshold</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.exemptionThreshold} years
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Lamports Per Byte Year</td>
|
||||
<td className="text-lg-right">
|
||||
{sysvarAccount.info.lamportsPerByteYear}
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SysvarAccountRewardsCard({
|
||||
account,
|
||||
sysvarAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
sysvarAccount: SysvarRewardsAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
|
||||
const validatorPointValueFormatted = new Intl.NumberFormat("en-US", {
|
||||
maximumSignificantDigits: 20,
|
||||
}).format(sysvarAccount.info.validatorPointValue);
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Sysvar Rewards"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>Validator Point Value</td>
|
||||
<td className="text-lg-right text-monospace">
|
||||
{validatorPointValueFormatted} lamports
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import React from "react";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { Address } from "components/common/Address";
|
||||
import { VoteAccount } from "validators/accounts/vote";
|
||||
import { displayTimestamp } from "utils/date";
|
||||
import {
|
||||
AccountHeader,
|
||||
AccountAddressRow,
|
||||
AccountBalanceRow,
|
||||
} from "components/common/Account";
|
||||
|
||||
export function VoteAccountSection({
|
||||
account,
|
||||
voteAccount,
|
||||
}: {
|
||||
account: Account;
|
||||
voteAccount: VoteAccount;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<AccountHeader
|
||||
title="Vote Account"
|
||||
refresh={() => refresh(account.pubkey)}
|
||||
/>
|
||||
|
||||
<TableCardBody>
|
||||
<AccountAddressRow account={account} />
|
||||
<AccountBalanceRow account={account} />
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Authorized Voter
|
||||
{voteAccount.info.authorizedVoters.length > 1 ? "s" : ""}
|
||||
</td>
|
||||
<td className="text-lg-right">
|
||||
{voteAccount.info.authorizedVoters.map((voter) => {
|
||||
return (
|
||||
<Address
|
||||
pubkey={voter.authorizedVoter}
|
||||
key={voter.authorizedVoter.toString()}
|
||||
alignRight
|
||||
raw
|
||||
link
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Authorized Withdrawer</td>
|
||||
<td className="text-lg-right">
|
||||
<Address
|
||||
pubkey={voteAccount.info.authorizedWithdrawer}
|
||||
alignRight
|
||||
raw
|
||||
link
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Last Timestamp</td>
|
||||
<td className="text-lg-right">
|
||||
{displayTimestamp(voteAccount.info.lastTimestamp.timestamp * 1000)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Commission</td>
|
||||
<td className="text-lg-right">{voteAccount.info.commission + "%"}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Root Slot</td>
|
||||
<td className="text-lg-right">{voteAccount.info.rootSlot}</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
import { VoteAccount, Vote } from "validators/accounts/vote";
|
||||
|
||||
export function VotesCard({ voteAccount }: { voteAccount: VoteAccount }) {
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h3 className="card-header-title">Vote History</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="w-1 text-muted">Slot</th>
|
||||
<th className="text-muted">Confirmation Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">
|
||||
{voteAccount.info.votes.length > 0 &&
|
||||
voteAccount.info.votes
|
||||
.reverse()
|
||||
.map((vote: Vote, index) => renderAccountRow(vote, index))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
<div className="text-muted text-center">
|
||||
{voteAccount.info.votes.length > 0 ? "" : "No votes found"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const renderAccountRow = (vote: Vote, index: number) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="w-1 text-monospace">
|
||||
{vote.slot.toLocaleString("en-US")}
|
||||
</td>
|
||||
<td className="text-monospace">{vote.confirmationCount}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
import React from "react";
|
||||
import { Address } from "./Address";
|
||||
import { Account } from "providers/accounts";
|
||||
import { lamportsToSolString } from "utils";
|
||||
|
||||
type AccountHeaderProps = {
|
||||
title: string;
|
||||
refresh: Function;
|
||||
};
|
||||
|
||||
type AccountProps = {
|
||||
account: Account;
|
||||
};
|
||||
|
||||
export function AccountHeader({ title, refresh }: AccountHeaderProps) {
|
||||
return (
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">{title}</h3>
|
||||
<button className="btn btn-white btn-sm" onClick={() => refresh()}>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountAddressRow({ account }: AccountProps) {
|
||||
return (
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.pubkey} alignRight raw />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountBalanceRow({ account }: AccountProps) {
|
||||
const { lamports } = account;
|
||||
return (
|
||||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-right text-uppercase">
|
||||
{lamportsToSolString(lamports)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountOwnerRow({ account }: AccountProps) {
|
||||
if (account.details) {
|
||||
return (
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.details.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
}
|
|
@ -5,6 +5,7 @@ import {
|
|||
useFetchAccountInfo,
|
||||
useAccountInfo,
|
||||
Account,
|
||||
ProgramData,
|
||||
} from "providers/accounts";
|
||||
import { StakeAccountSection } from "components/account/StakeAccountSection";
|
||||
import { TokenAccountSection } from "components/account/TokenAccountSection";
|
||||
|
@ -19,6 +20,50 @@ import { TransactionHistoryCard } from "components/account/TransactionHistoryCar
|
|||
import { TokenHistoryCard } from "components/account/TokenHistoryCard";
|
||||
import { TokenLargestAccountsCard } from "components/account/TokenLargestAccountsCard";
|
||||
import { TokenRegistry } from "tokenRegistry";
|
||||
import { VoteAccountSection } from "components/account/VoteAccountSection";
|
||||
import { NonceAccountSection } from "components/account/NonceAccountSection";
|
||||
import { VotesCard } from "components/account/VotesCard";
|
||||
import { SysvarAccountSection } from "components/account/SysvarAccountSection";
|
||||
import { SlotHashesCard } from "components/account/SlotHashesCard";
|
||||
import { StakeHistoryCard } from "components/account/StakeHistoryCard";
|
||||
import { BlockhashesCard } from "components/account/BlockhashesCard";
|
||||
import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
||||
|
||||
const TABS_LOOKUP: { [id: string]: Tab } = {
|
||||
"spl-token:mint": {
|
||||
slug: "largest",
|
||||
title: "Distribution",
|
||||
path: "/largest",
|
||||
},
|
||||
vote: {
|
||||
slug: "vote-history",
|
||||
title: "Vote History",
|
||||
path: "/vote-history",
|
||||
},
|
||||
"sysvar:recentBlockhashes": {
|
||||
slug: "blockhashes",
|
||||
title: "Blockhashes",
|
||||
path: "/blockhashes",
|
||||
},
|
||||
"sysvar:slotHashes": {
|
||||
slug: "slot-hashes",
|
||||
title: "Slot Hashes",
|
||||
path: "/slot-hashes",
|
||||
},
|
||||
"sysvar:stakeHistory": {
|
||||
slug: "stake-history",
|
||||
title: "Stake History",
|
||||
path: "/stake-history",
|
||||
},
|
||||
};
|
||||
|
||||
const TOKEN_TABS_HIDDEN = [
|
||||
"spl-token:mint",
|
||||
"config",
|
||||
"vote",
|
||||
"sysvar",
|
||||
"config",
|
||||
];
|
||||
|
||||
type Props = { address: string; tab?: string };
|
||||
export function AccountDetailsPage({ address, tab }: Props) {
|
||||
|
@ -101,30 +146,7 @@ function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
|
|||
|
||||
const account = info.data;
|
||||
const data = account?.details?.data;
|
||||
|
||||
let tabs: Tab[] = [
|
||||
{
|
||||
slug: "history",
|
||||
title: "History",
|
||||
path: "",
|
||||
},
|
||||
];
|
||||
|
||||
if (data && data?.program === "spl-token") {
|
||||
if (data.parsed.type === "mint") {
|
||||
tabs.push({
|
||||
slug: "largest",
|
||||
title: "Distribution",
|
||||
path: "/largest",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
tabs.push({
|
||||
slug: "tokens",
|
||||
title: "Tokens",
|
||||
path: "/tokens",
|
||||
});
|
||||
}
|
||||
const tabs = getTabs(data);
|
||||
|
||||
let moreTab: MoreTabs = "history";
|
||||
if (tab && tabs.filter(({ slug }) => slug === tab).length === 0) {
|
||||
|
@ -164,6 +186,18 @@ function InfoSection({ account }: { account: Account }) {
|
|||
);
|
||||
} else if (data && data.program === "spl-token") {
|
||||
return <TokenAccountSection account={account} tokenAccount={data.parsed} />;
|
||||
} else if (data && data.program === "nonce") {
|
||||
return <NonceAccountSection account={account} nonceAccount={data.parsed} />;
|
||||
} else if (data && data.program === "vote") {
|
||||
return <VoteAccountSection account={account} voteAccount={data.parsed} />;
|
||||
} else if (data && data.program === "sysvar") {
|
||||
return (
|
||||
<SysvarAccountSection account={account} sysvarAccount={data.parsed} />
|
||||
);
|
||||
} else if (data && data.program === "config") {
|
||||
return (
|
||||
<ConfigAccountSection account={account} configAccount={data.parsed} />
|
||||
);
|
||||
} else {
|
||||
return <UnknownAccountCard account={account} />;
|
||||
}
|
||||
|
@ -175,7 +209,15 @@ type Tab = {
|
|||
path: string;
|
||||
};
|
||||
|
||||
type MoreTabs = "history" | "tokens" | "largest";
|
||||
type MoreTabs =
|
||||
| "history"
|
||||
| "tokens"
|
||||
| "largest"
|
||||
| "vote-history"
|
||||
| "slot-hashes"
|
||||
| "stake-history"
|
||||
| "blockhashes";
|
||||
|
||||
function MoreSection({
|
||||
account,
|
||||
tab,
|
||||
|
@ -187,7 +229,7 @@ function MoreSection({
|
|||
}) {
|
||||
const pubkey = account.pubkey;
|
||||
const address = account.pubkey.toBase58();
|
||||
|
||||
const data = account?.details?.data;
|
||||
return (
|
||||
<>
|
||||
<div className="container">
|
||||
|
@ -217,6 +259,63 @@ function MoreSection({
|
|||
)}
|
||||
{tab === "history" && <TransactionHistoryCard pubkey={pubkey} />}
|
||||
{tab === "largest" && <TokenLargestAccountsCard pubkey={pubkey} />}
|
||||
{tab === "vote-history" && data?.program === "vote" && (
|
||||
<VotesCard voteAccount={data.parsed} />
|
||||
)}
|
||||
{tab === "slot-hashes" &&
|
||||
data?.program === "sysvar" &&
|
||||
data.parsed.type === "slotHashes" && (
|
||||
<SlotHashesCard sysvarAccount={data.parsed} />
|
||||
)}
|
||||
{tab === "stake-history" &&
|
||||
data?.program === "sysvar" &&
|
||||
data.parsed.type === "stakeHistory" && (
|
||||
<StakeHistoryCard sysvarAccount={data.parsed} />
|
||||
)}
|
||||
{tab === "blockhashes" &&
|
||||
data?.program === "sysvar" &&
|
||||
data.parsed.type === "recentBlockhashes" && (
|
||||
<BlockhashesCard blockhashes={data.parsed.info} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getTabs(data?: ProgramData): Tab[] {
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
slug: "history",
|
||||
title: "History",
|
||||
path: "",
|
||||
},
|
||||
];
|
||||
|
||||
let programTypeKey = "";
|
||||
if (data && "type" in data.parsed) {
|
||||
programTypeKey = `${data.program}:${data.parsed.type}`;
|
||||
}
|
||||
|
||||
if (data && data.program in TABS_LOOKUP) {
|
||||
tabs.push(TABS_LOOKUP[data.program]);
|
||||
}
|
||||
|
||||
if (data && programTypeKey in TABS_LOOKUP) {
|
||||
tabs.push(TABS_LOOKUP[programTypeKey]);
|
||||
}
|
||||
|
||||
if (
|
||||
!data ||
|
||||
!(
|
||||
TOKEN_TABS_HIDDEN.includes(data.program) ||
|
||||
TOKEN_TABS_HIDDEN.includes(programTypeKey)
|
||||
)
|
||||
) {
|
||||
tabs.push({
|
||||
slug: "tokens",
|
||||
title: "Tokens",
|
||||
path: "/tokens",
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ import {
|
|||
import * as Cache from "providers/cache";
|
||||
import { ActionType, FetchStatus } from "providers/cache";
|
||||
import { reportError } from "utils/sentry";
|
||||
import { VoteAccount } from "validators/accounts/vote";
|
||||
import { NonceAccount } from "validators/accounts/nonce";
|
||||
import { SysvarAccount } from "validators/accounts/sysvar";
|
||||
import { ConfigAccount } from "validators/accounts/config";
|
||||
export { useAccountHistory } from "./history";
|
||||
|
||||
export type StakeProgramData = {
|
||||
|
@ -33,7 +37,33 @@ export type TokenProgramData = {
|
|||
parsed: TokenAccount;
|
||||
};
|
||||
|
||||
export type ProgramData = StakeProgramData | TokenProgramData;
|
||||
export type VoteProgramData = {
|
||||
program: "vote";
|
||||
parsed: VoteAccount;
|
||||
};
|
||||
|
||||
export type NonceProgramData = {
|
||||
program: "nonce";
|
||||
parsed: NonceAccount;
|
||||
};
|
||||
|
||||
export type SysvarProgramData = {
|
||||
program: "sysvar";
|
||||
parsed: SysvarAccount;
|
||||
};
|
||||
|
||||
export type ConfigProgramData = {
|
||||
program: "config";
|
||||
parsed: ConfigAccount;
|
||||
};
|
||||
|
||||
export type ProgramData =
|
||||
| StakeProgramData
|
||||
| TokenProgramData
|
||||
| VoteProgramData
|
||||
| NonceProgramData
|
||||
| SysvarProgramData
|
||||
| ConfigProgramData;
|
||||
|
||||
export interface Details {
|
||||
executable: boolean;
|
||||
|
@ -134,20 +164,54 @@ async function fetchAccountInfo(
|
|||
reportError(err, { url, address: pubkey.toBase58() });
|
||||
// TODO store error state in Account info
|
||||
}
|
||||
} else if (
|
||||
"parsed" in result.data &&
|
||||
result.owner.equals(TOKEN_PROGRAM_ID)
|
||||
) {
|
||||
try {
|
||||
const info = coerce(result.data.parsed, ParsedInfo);
|
||||
const parsed = coerce(info, TokenAccount);
|
||||
data = {
|
||||
program: "spl-token",
|
||||
parsed,
|
||||
};
|
||||
} catch (err) {
|
||||
reportError(err, { url, address: pubkey.toBase58() });
|
||||
// TODO store error state in Account info
|
||||
}
|
||||
} else if ("parsed" in result.data) {
|
||||
if (result.owner.equals(TOKEN_PROGRAM_ID)) {
|
||||
try {
|
||||
const info = coerce(result.data.parsed, ParsedInfo);
|
||||
const parsed = coerce(info, TokenAccount);
|
||||
|
||||
data = {
|
||||
program: "spl-token",
|
||||
parsed,
|
||||
};
|
||||
} catch (err) {
|
||||
reportError(err, { url, address: pubkey.toBase58() });
|
||||
// TODO store error state in Account info
|
||||
try {
|
||||
const info = coerce(result.data.parsed, ParsedInfo);
|
||||
switch (result.data.program) {
|
||||
case "vote":
|
||||
data = {
|
||||
program: result.data.program,
|
||||
parsed: coerce(info, VoteAccount),
|
||||
};
|
||||
break;
|
||||
case "nonce":
|
||||
data = {
|
||||
program: result.data.program,
|
||||
parsed: coerce(info, NonceAccount),
|
||||
};
|
||||
break;
|
||||
case "sysvar":
|
||||
data = {
|
||||
program: result.data.program,
|
||||
parsed: coerce(info, SysvarAccount),
|
||||
};
|
||||
break;
|
||||
case "config":
|
||||
data = {
|
||||
program: result.data.program,
|
||||
parsed: coerce(info, ConfigAccount),
|
||||
};
|
||||
break;
|
||||
default:
|
||||
data = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
reportError(error, { url, address: pubkey.toBase58() });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
StructType,
|
||||
pick,
|
||||
array,
|
||||
boolean,
|
||||
object,
|
||||
number,
|
||||
string,
|
||||
record,
|
||||
union,
|
||||
literal,
|
||||
} from "superstruct";
|
||||
|
||||
export type StakeConfigInfo = StructType<typeof StakeConfigInfo>;
|
||||
export const StakeConfigInfo = pick({
|
||||
warmupCooldownRate: number(),
|
||||
slashPenalty: number(),
|
||||
});
|
||||
|
||||
export type ConfigKey = StructType<typeof ConfigKey>;
|
||||
export const ConfigKey = pick({
|
||||
pubkey: string(),
|
||||
signer: boolean(),
|
||||
});
|
||||
|
||||
export type ValidatorInfoConfigData = StructType<
|
||||
typeof ValidatorInfoConfigData
|
||||
>;
|
||||
export const ValidatorInfoConfigData = record(string(), string());
|
||||
|
||||
export type ValidatorInfoConfigInfo = StructType<
|
||||
typeof ValidatorInfoConfigInfo
|
||||
>;
|
||||
export const ValidatorInfoConfigInfo = pick({
|
||||
keys: array(ConfigKey),
|
||||
configData: ValidatorInfoConfigData,
|
||||
});
|
||||
|
||||
export type ValidatorInfoAccount = StructType<typeof ValidatorInfoAccount>;
|
||||
export const ValidatorInfoAccount = object({
|
||||
type: literal("validatorInfo"),
|
||||
info: ValidatorInfoConfigInfo,
|
||||
});
|
||||
|
||||
export type StakeConfigInfoAccount = StructType<typeof StakeConfigInfoAccount>;
|
||||
export const StakeConfigInfoAccount = object({
|
||||
type: literal("stakeConfig"),
|
||||
info: StakeConfigInfo,
|
||||
});
|
||||
|
||||
export type ConfigAccount = StructType<typeof ConfigAccount>;
|
||||
export const ConfigAccount = union([
|
||||
StakeConfigInfoAccount,
|
||||
ValidatorInfoAccount,
|
||||
]);
|
|
@ -0,0 +1,20 @@
|
|||
import { StructType, object, string, enums, pick } from "superstruct";
|
||||
import { Pubkey } from "validators/pubkey";
|
||||
|
||||
export type NonceAccountType = StructType<typeof NonceAccountType>;
|
||||
export const NonceAccountType = enums(["uninitialized", "initialized"]);
|
||||
|
||||
export type NonceAccountInfo = StructType<typeof NonceAccountInfo>;
|
||||
export const NonceAccountInfo = pick({
|
||||
authority: Pubkey,
|
||||
blockhash: string(),
|
||||
feeCalculator: pick({
|
||||
lamportsPerSignature: string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type NonceAccount = StructType<typeof NonceAccount>;
|
||||
export const NonceAccount = object({
|
||||
type: NonceAccountType,
|
||||
info: NonceAccountInfo,
|
||||
});
|
|
@ -0,0 +1,180 @@
|
|||
import {
|
||||
StructType,
|
||||
enums,
|
||||
array,
|
||||
number,
|
||||
object,
|
||||
boolean,
|
||||
string,
|
||||
pick,
|
||||
literal,
|
||||
union,
|
||||
} from "superstruct";
|
||||
|
||||
export type SysvarAccountType = StructType<typeof SysvarAccountType>;
|
||||
export const SysvarAccountType = enums([
|
||||
"clock",
|
||||
"epochSchedule",
|
||||
"fees",
|
||||
"recentBlockhashes",
|
||||
"rent",
|
||||
"rewards",
|
||||
"slotHashes",
|
||||
"slotHistory",
|
||||
"stakeHistory",
|
||||
]);
|
||||
|
||||
export type ClockAccountInfo = StructType<typeof ClockAccountInfo>;
|
||||
export const ClockAccountInfo = pick({
|
||||
slot: number(),
|
||||
epoch: number(),
|
||||
leaderScheduleEpoch: number(),
|
||||
unixTimestamp: number(),
|
||||
});
|
||||
|
||||
export type SysvarClockAccount = StructType<typeof SysvarClockAccount>;
|
||||
export const SysvarClockAccount = object({
|
||||
type: literal("clock"),
|
||||
info: ClockAccountInfo,
|
||||
});
|
||||
|
||||
export type EpochScheduleInfo = StructType<typeof EpochScheduleInfo>;
|
||||
export const EpochScheduleInfo = pick({
|
||||
slotsPerEpoch: number(),
|
||||
leaderScheduleSlotOffset: number(),
|
||||
warmup: boolean(),
|
||||
firstNormalEpoch: number(),
|
||||
firstNormalSlot: number(),
|
||||
});
|
||||
|
||||
export type SysvarEpochScheduleAccount = StructType<
|
||||
typeof SysvarEpochScheduleAccount
|
||||
>;
|
||||
export const SysvarEpochScheduleAccount = object({
|
||||
type: literal("epochSchedule"),
|
||||
info: EpochScheduleInfo,
|
||||
});
|
||||
|
||||
export type FeesInfo = StructType<typeof FeesInfo>;
|
||||
export const FeesInfo = pick({
|
||||
feeCalculator: pick({
|
||||
lamportsPerSignature: string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type SysvarFeesAccount = StructType<typeof SysvarFeesAccount>;
|
||||
export const SysvarFeesAccount = object({
|
||||
type: literal("fees"),
|
||||
info: FeesInfo,
|
||||
});
|
||||
|
||||
export type RecentBlockhashesEntry = StructType<typeof RecentBlockhashesEntry>;
|
||||
export const RecentBlockhashesEntry = pick({
|
||||
blockhash: string(),
|
||||
feeCalculator: pick({
|
||||
lamportsPerSignature: string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type RecentBlockhashesInfo = StructType<typeof RecentBlockhashesInfo>;
|
||||
export const RecentBlockhashesInfo = array(RecentBlockhashesEntry);
|
||||
|
||||
export type SysvarRecentBlockhashesAccount = StructType<
|
||||
typeof SysvarRecentBlockhashesAccount
|
||||
>;
|
||||
export const SysvarRecentBlockhashesAccount = object({
|
||||
type: literal("recentBlockhashes"),
|
||||
info: RecentBlockhashesInfo,
|
||||
});
|
||||
|
||||
export type RentInfo = StructType<typeof RentInfo>;
|
||||
export const RentInfo = pick({
|
||||
lamportsPerByteYear: string(),
|
||||
exemptionThreshold: number(),
|
||||
burnPercent: number(),
|
||||
});
|
||||
|
||||
export type SysvarRentAccount = StructType<typeof SysvarRentAccount>;
|
||||
export const SysvarRentAccount = object({
|
||||
type: literal("rent"),
|
||||
info: RentInfo,
|
||||
});
|
||||
|
||||
export type RewardsInfo = StructType<typeof RewardsInfo>;
|
||||
export const RewardsInfo = pick({
|
||||
validatorPointValue: number(),
|
||||
});
|
||||
|
||||
export type SysvarRewardsAccount = StructType<typeof SysvarRewardsAccount>;
|
||||
export const SysvarRewardsAccount = object({
|
||||
type: literal("rewards"),
|
||||
info: RewardsInfo,
|
||||
});
|
||||
|
||||
export type SlotHashEntry = StructType<typeof SlotHashEntry>;
|
||||
export const SlotHashEntry = pick({
|
||||
slot: number(),
|
||||
hash: string(),
|
||||
});
|
||||
|
||||
export type SlotHashesInfo = StructType<typeof SlotHashesInfo>;
|
||||
export const SlotHashesInfo = array(SlotHashEntry);
|
||||
|
||||
export type SysvarSlotHashesAccount = StructType<
|
||||
typeof SysvarSlotHashesAccount
|
||||
>;
|
||||
export const SysvarSlotHashesAccount = object({
|
||||
type: literal("slotHashes"),
|
||||
info: SlotHashesInfo,
|
||||
});
|
||||
|
||||
export type SlotHistoryInfo = StructType<typeof SlotHistoryInfo>;
|
||||
export const SlotHistoryInfo = pick({
|
||||
nextSlot: number(),
|
||||
bits: string(),
|
||||
});
|
||||
|
||||
export type SysvarSlotHistoryAccount = StructType<
|
||||
typeof SysvarSlotHistoryAccount
|
||||
>;
|
||||
export const SysvarSlotHistoryAccount = object({
|
||||
type: literal("slotHistory"),
|
||||
info: SlotHistoryInfo,
|
||||
});
|
||||
|
||||
export type StakeHistoryEntryItem = StructType<typeof StakeHistoryEntryItem>;
|
||||
export const StakeHistoryEntryItem = pick({
|
||||
effective: number(),
|
||||
activating: number(),
|
||||
deactivating: number(),
|
||||
});
|
||||
|
||||
export type StakeHistoryEntry = StructType<typeof StakeHistoryEntry>;
|
||||
export const StakeHistoryEntry = pick({
|
||||
epoch: number(),
|
||||
stakeHistory: StakeHistoryEntryItem,
|
||||
});
|
||||
|
||||
export type StakeHistoryInfo = StructType<typeof StakeHistoryInfo>;
|
||||
export const StakeHistoryInfo = array(StakeHistoryEntry);
|
||||
|
||||
export type SysvarStakeHistoryAccount = StructType<
|
||||
typeof SysvarStakeHistoryAccount
|
||||
>;
|
||||
export const SysvarStakeHistoryAccount = object({
|
||||
type: literal("stakeHistory"),
|
||||
info: StakeHistoryInfo,
|
||||
});
|
||||
|
||||
export type SysvarAccount = StructType<typeof SysvarAccount>;
|
||||
export const SysvarAccount = union([
|
||||
SysvarClockAccount,
|
||||
SysvarEpochScheduleAccount,
|
||||
SysvarFeesAccount,
|
||||
SysvarRecentBlockhashesAccount,
|
||||
SysvarRentAccount,
|
||||
SysvarRewardsAccount,
|
||||
SysvarSlotHashesAccount,
|
||||
SysvarSlotHistoryAccount,
|
||||
SysvarStakeHistoryAccount,
|
||||
]);
|
|
@ -0,0 +1,62 @@
|
|||
import {
|
||||
StructType,
|
||||
enums,
|
||||
pick,
|
||||
number,
|
||||
array,
|
||||
object,
|
||||
nullable,
|
||||
string,
|
||||
} from "superstruct";
|
||||
import { Pubkey } from "validators/pubkey";
|
||||
|
||||
export type VoteAccountType = StructType<typeof VoteAccountType>;
|
||||
export const VoteAccountType = enums(["vote"]);
|
||||
|
||||
export type AuthorizedVoter = StructType<typeof AuthorizedVoter>;
|
||||
export const AuthorizedVoter = pick({
|
||||
authorizedVoter: Pubkey,
|
||||
epoch: number(),
|
||||
});
|
||||
|
||||
export type PriorVoter = StructType<typeof PriorVoter>;
|
||||
export const PriorVoter = pick({
|
||||
authorizedPubkey: Pubkey,
|
||||
epochOfLastAuthorizedSwitch: number(),
|
||||
targetEpoch: number(),
|
||||
});
|
||||
|
||||
export type EpochCredits = StructType<typeof EpochCredits>;
|
||||
export const EpochCredits = pick({
|
||||
epoch: number(),
|
||||
credits: string(),
|
||||
previousCredits: string(),
|
||||
});
|
||||
|
||||
export type Vote = StructType<typeof Vote>;
|
||||
export const Vote = object({
|
||||
slot: number(),
|
||||
confirmationCount: number(),
|
||||
});
|
||||
|
||||
export type VoteAccountInfo = StructType<typeof VoteAccountInfo>;
|
||||
export const VoteAccountInfo = pick({
|
||||
authorizedVoters: array(AuthorizedVoter),
|
||||
authorizedWithdrawer: Pubkey,
|
||||
commission: number(),
|
||||
epochCredits: array(EpochCredits),
|
||||
lastTimestamp: object({
|
||||
slot: number(),
|
||||
timestamp: number(),
|
||||
}),
|
||||
nodePubkey: Pubkey,
|
||||
priorVoters: array(PriorVoter),
|
||||
rootSlot: nullable(number()),
|
||||
votes: array(Vote),
|
||||
});
|
||||
|
||||
export type VoteAccount = StructType<typeof VoteAccount>;
|
||||
export const VoteAccount = pick({
|
||||
type: VoteAccountType,
|
||||
info: VoteAccountInfo,
|
||||
});
|
Loading…
Reference in New Issue