Add token registry to explorer (#11612)

This commit is contained in:
Justin Starry 2020-08-13 22:57:53 +08:00 committed by GitHub
parent 00a8f90f79
commit 6162c2d0d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 438 additions and 111 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -3,13 +3,16 @@ import bs58 from "bs58";
import { useHistory, useLocation } from "react-router-dom";
import Select, { InputActionMeta, ActionMeta, ValueType } from "react-select";
import StateManager from "react-select";
import { PROGRAM_IDS, SYSVAR_IDS } from "utils/tx";
import { PROGRAM_IDS, SYSVAR_IDS, ProgramName } from "utils/tx";
import { TokenRegistry } from "tokenRegistry";
import { Cluster, useCluster } from "providers/cluster";
export function SearchBar() {
const [search, setSearch] = React.useState("");
const selectRef = React.useRef<StateManager<any> | null>(null);
const history = useHistory();
const location = useLocation();
const { cluster } = useCluster();
const onChange = ({ pathname }: ValueType<any>, meta: ActionMeta<any>) => {
if (meta.action === "select-option") {
@ -29,9 +32,9 @@ export function SearchBar() {
<div className="col">
<Select
ref={(ref) => (selectRef.current = ref)}
options={buildOptions(search)}
options={buildOptions(search, cluster)}
noOptionsMessage={() => "No Results"}
placeholder="Search by address or signature"
placeholder="Search for accounts, transactions, programs, and tokens"
value={resetValue}
inputValue={search}
blurInputOnSelect
@ -47,22 +50,31 @@ export function SearchBar() {
);
}
const SEARCHABLE_PROGRAMS = ["Config", "Stake", "System", "Vote", "Token"];
const SEARCHABLE_PROGRAMS: ProgramName[] = [
"Config Program",
"Stake Program",
"System Program",
"Vote Program",
"SPL Token",
];
function buildProgramOptions(search: string) {
const matchedPrograms = Object.entries(PROGRAM_IDS).filter(([, name]) => {
return (
SEARCHABLE_PROGRAMS.includes(name) &&
name.toLowerCase().includes(search.toLowerCase())
);
});
const matchedPrograms = Object.entries(PROGRAM_IDS).filter(
([address, name]) => {
return (
SEARCHABLE_PROGRAMS.includes(name) &&
(name.toLowerCase().includes(search.toLowerCase()) ||
address.includes(search))
);
}
);
if (matchedPrograms.length > 0) {
return {
label: "Programs",
options: matchedPrograms.map(([id, name]) => ({
label: name,
value: name,
value: [name, id],
pathname: "/address/" + id,
})),
};
@ -70,23 +82,52 @@ function buildProgramOptions(search: string) {
}
function buildSysvarOptions(search: string) {
const matchedSysvars = Object.entries(SYSVAR_IDS).filter(([, name]) => {
return name.toLowerCase().includes(search.toLowerCase());
});
const matchedSysvars = Object.entries(SYSVAR_IDS).filter(
([address, name]) => {
return (
name.toLowerCase().includes(search.toLowerCase()) ||
address.includes(search)
);
}
);
if (matchedSysvars.length > 0) {
return {
label: "Sysvars",
options: matchedSysvars.map(([id, name]) => ({
label: name,
value: name,
value: [name, id],
pathname: "/address/" + id,
})),
};
}
}
function buildOptions(search: string) {
function buildTokenOptions(search: string, cluster: Cluster) {
const matchedTokens = Object.entries(TokenRegistry.all(cluster)).filter(
([address, details]) => {
const searchLower = search.toLowerCase();
return (
details.name.toLowerCase().includes(searchLower) ||
details.symbol.toLowerCase().includes(searchLower) ||
address.includes(search)
);
}
);
if (matchedTokens.length > 0) {
return {
label: "Tokens",
options: matchedTokens.map(([id, details]) => ({
label: details.name,
value: [details.name, details.symbol, id],
pathname: "/address/" + id,
})),
};
}
}
function buildOptions(search: string, cluster: Cluster) {
if (search.length === 0) return [];
const options = [];
@ -101,6 +142,14 @@ function buildOptions(search: string) {
options.push(sysvarOptions);
}
const tokenOptions = buildTokenOptions(search, cluster);
if (tokenOptions) {
options.push(tokenOptions);
}
// Prefer nice suggestions over raw suggestions
if (options.length > 0) return options;
try {
const decoded = bs58.decode(search);
if (decoded.length === 32) {

View File

@ -4,16 +4,36 @@ import { FetchStatus } from "providers/cache";
import {
useFetchAccountOwnedTokens,
useAccountOwnedTokens,
TokenInfoWithPubkey,
} from "providers/accounts/tokens";
import { ErrorCard } from "components/common/ErrorCard";
import { LoadingCard } from "components/common/LoadingCard";
import { Address } from "components/common/Address";
import { TokenRegistry } from "tokenRegistry";
import { useQuery } from "utils/url";
import { Link } from "react-router-dom";
import { Location } from "history";
import { useCluster } from "providers/cluster";
type Display = "summary" | "detail" | null;
const useQueryDisplay = (): Display => {
const query = useQuery();
const filter = query.get("display");
if (filter === "summary" || filter === "detail") {
return filter;
} else {
return null;
}
};
export function OwnedTokensCard({ pubkey }: { pubkey: PublicKey }) {
const address = pubkey.toBase58();
const ownedTokens = useAccountOwnedTokens(address);
const fetchAccountTokens = useFetchAccountOwnedTokens();
const refresh = () => fetchAccountTokens(pubkey);
const [showDropdown, setDropdown] = React.useState(false);
const display = useQueryDisplay();
// Fetch owned tokens
React.useEffect(() => {
@ -28,9 +48,9 @@ export function OwnedTokensCard({ pubkey }: { pubkey: PublicKey }) {
const tokens = ownedTokens.data?.tokens;
const fetching = status === FetchStatus.Fetching;
if (fetching && (tokens === undefined || tokens.length === 0)) {
return <LoadingCard message="Loading owned tokens" />;
return <LoadingCard message="Loading token holdings" />;
} else if (tokens === undefined) {
return <ErrorCard retry={refresh} text="Failed to fetch owned tokens" />;
return <ErrorCard retry={refresh} text="Failed to fetch token holdings" />;
}
if (tokens.length === 0) {
@ -38,17 +58,100 @@ export function OwnedTokensCard({ pubkey }: { pubkey: PublicKey }) {
<ErrorCard
retry={refresh}
retryText="Try Again"
text={"No owned tokens found"}
text={"No token holdings found"}
/>
);
}
return (
<>
{showDropdown && (
<div className="dropdown-exit" onClick={() => setDropdown(false)} />
)}
<div className="card">
<div className="card-header align-items-center">
<h3 className="card-header-title">Token Holdings</h3>
<DisplayDropdown
display={display}
toggle={() => setDropdown((show) => !show)}
show={showDropdown}
/>
</div>
{display === "detail" ? (
<HoldingsDetailTable tokens={tokens} />
) : (
<HoldingsSummaryTable tokens={tokens} />
)}
</div>
</>
);
}
function HoldingsDetailTable({ tokens }: { tokens: TokenInfoWithPubkey[] }) {
const detailsList: React.ReactNode[] = [];
const { cluster } = useCluster();
const showLogos = tokens.some(
(t) => TokenRegistry.get(t.info.mint.toBase58(), cluster) !== undefined
);
tokens.forEach((tokenAccount) => {
const address = tokenAccount.pubkey.toBase58();
const mintAddress = tokenAccount.info.mint.toBase58();
const tokenDetails = TokenRegistry.get(mintAddress, cluster);
detailsList.push(
<tr key={address}>
{showLogos && (
<td className="w-1 p-0 text-center">
{tokenDetails && (
<img
src={tokenDetails.icon}
alt="token icon"
className="token-icon rounded-circle border border-4 border-gray-dark"
/>
)}
</td>
)}
<td>
<Address pubkey={tokenAccount.pubkey} link truncate />
</td>
<td>
<Address pubkey={tokenAccount.info.mint} link truncate />
</td>
<td>
{tokenAccount.info.tokenAmount.uiAmount}{" "}
{tokenDetails && tokenDetails.symbol}
</td>
</tr>
);
});
return (
<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<thead>
<tr>
{showLogos && (
<th className="text-muted w-1 p-0 text-center">Logo</th>
)}
<th className="text-muted">Account Address</th>
<th className="text-muted">Mint Address</th>
<th className="text-muted">Balance</th>
</tr>
</thead>
<tbody className="list">{detailsList}</tbody>
</table>
</div>
);
}
function HoldingsSummaryTable({ tokens }: { tokens: TokenInfoWithPubkey[] }) {
const { cluster } = useCluster();
const mappedTokens = new Map<string, number>();
for (const { info: token } of tokens) {
const mintAddress = token.mint.toBase58();
const totalByMint = mappedTokens.get(mintAddress);
let amount = token?.amount || (token?.tokenAmount?.uiAmount as number);
let amount = token.tokenAmount.uiAmount;
if (totalByMint !== undefined) {
amount += totalByMint;
}
@ -57,51 +160,100 @@ export function OwnedTokensCard({ pubkey }: { pubkey: PublicKey }) {
}
const detailsList: React.ReactNode[] = [];
const showLogos = tokens.some((t) =>
TokenRegistry.get(t.info.mint.toBase58(), cluster)
);
mappedTokens.forEach((totalByMint, mintAddress) => {
const tokenDetails = TokenRegistry.get(mintAddress, cluster);
detailsList.push(
<tr key={mintAddress}>
{showLogos && (
<td className="w-1 p-0 text-center">
{tokenDetails && (
<img
src={tokenDetails.icon}
alt="token icon"
className="token-icon rounded-circle border border-4 border-gray-dark"
/>
)}
</td>
)}
<td>
<Address pubkey={new PublicKey(mintAddress)} link />
</td>
<td>{totalByMint}</td>
<td>
{totalByMint} {tokenDetails && tokenDetails.symbol}
</td>
</tr>
);
});
return (
<div className="card">
<div className="card-header align-items-center">
<h3 className="card-header-title">Owned Tokens</h3>
<button
className="btn btn-white btn-sm"
disabled={fetching}
onClick={refresh}
>
{fetching ? (
<>
<span className="spinner-grow spinner-grow-sm mr-2"></span>
Loading
</>
) : (
<>
<span className="fe fe-refresh-cw mr-2"></span>
Refresh
</>
)}
</button>
</div>
<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<thead>
<tr>
<th className="text-muted">Token Address</th>
<th className="text-muted">Balance</th>
</tr>
</thead>
<tbody className="list">{detailsList}</tbody>
</table>
</div>
<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<thead>
<tr>
{showLogos && (
<th className="text-muted w-1 p-0 text-center">Logo</th>
)}
<th className="text-muted">Mint Address</th>
<th className="text-muted">Total Balance</th>
</tr>
</thead>
<tbody className="list">{detailsList}</tbody>
</table>
</div>
);
}
type DropdownProps = {
display: Display;
toggle: () => void;
show: boolean;
};
const DisplayDropdown = ({ display, toggle, show }: DropdownProps) => {
const buildLocation = (location: Location, display: Display) => {
const params = new URLSearchParams(location.search);
if (display === null) {
params.delete("display");
} else {
params.set("display", display);
}
return {
...location,
search: params.toString(),
};
};
const DISPLAY_OPTIONS: Display[] = [null, "detail"];
return (
<div className="dropdown">
<button
className="btn btn-white btn-sm dropdown-toggle"
type="button"
onClick={toggle}
>
{display === "detail" ? "Detailed" : "Summary"}
</button>
<div
className={`dropdown-menu-right dropdown-menu${show ? " show" : ""}`}
>
{DISPLAY_OPTIONS.map((displayOption) => {
return (
<Link
key={displayOption || "null"}
to={(location) => buildLocation(location, displayOption)}
className={`dropdown-item${
displayOption === display ? " active" : ""
}`}
onClick={toggle}
>
{displayOption === "detail" ? "Detailed" : "Summary"}
</Link>
);
})}
</div>
</div>
);
};

View File

@ -12,6 +12,8 @@ import { Address } from "components/common/Address";
import { UnknownAccountCard } from "./UnknownAccountCard";
import { useFetchTokenSupply, useTokenSupply } from "providers/mints/supply";
import { FetchStatus } from "providers/cache";
import { TokenRegistry } from "tokenRegistry";
import { useCluster } from "providers/cluster";
export function TokenAccountSection({
account,
@ -46,6 +48,7 @@ function MintAccountCard({
account: Account;
info: MintAccountInfo;
}) {
const { cluster } = useCluster();
const mintAddress = account.pubkey.toBase58();
const fetchInfo = useFetchAccountInfo();
const supply = useTokenSupply(mintAddress);
@ -70,18 +73,20 @@ function MintAccountCard({
renderSupply = "Fetch failed";
}
} else {
renderSupply = supplyTotal;
const unit = TokenRegistry.get(mintAddress, cluster)?.symbol;
renderSupply = unit ? `${supplyTotal} ${unit}` : supplyTotal;
}
React.useEffect(() => {
if (!supply) refreshSupply();
}, [mintAddress]); // eslint-disable-line react-hooks/exhaustive-deps
const tokenInfo = TokenRegistry.get(mintAddress, cluster);
return (
<div className="card">
<div className="card-header">
<h3 className="card-header-title mb-0 d-flex align-items-center">
Token Mint Account
{tokenInfo ? "Overview" : "Token Mint"}
</h3>
<button className="btn btn-white btn-sm" onClick={refresh}>
<span className="fe fe-refresh-cw mr-2"></span>
@ -100,6 +105,21 @@ function MintAccountCard({
<td>Total Supply</td>
<td className="text-lg-right">{renderSupply}</td>
</tr>
{tokenInfo && (
<tr>
<td>Website</td>
<td className="text-lg-right">
<a
rel="noopener noreferrer"
target="_blank"
href={tokenInfo.website}
>
{tokenInfo.website}
<span className="fe fe-external-link ml-2"></span>
</a>
</td>
</tr>
)}
<tr>
<td>Decimals</td>
<td className="text-lg-right">{info.decimals}</td>
@ -131,13 +151,11 @@ function TokenAccountCard({
info: TokenAccountInfo;
}) {
const refresh = useFetchAccountInfo();
const { cluster } = useCluster();
let balance;
if ("amount" in info) {
balance = info.amount;
} else {
balance = info.tokenAmount?.uiAmount;
}
const balance = info.tokenAmount?.uiAmount;
const unit =
TokenRegistry.get(info.mint.toBase58(), cluster)?.symbol || "tokens";
return (
<div className="card">
@ -174,15 +192,15 @@ function TokenAccountCard({
</td>
</tr>
<tr>
<td>Balance (tokens)</td>
<td>Balance ({unit})</td>
<td className="text-lg-right">{balance}</td>
</tr>
<tr>
<td>Status</td>
<td className="text-lg-right">
{info.isInitialized ? "Initialized" : "Uninitialized"}
</td>
</tr>
{!info.isInitialized && (
<tr>
<td>Status</td>
<td className="text-lg-right">Uninitialized</td>
</tr>
)}
</TableCardBody>
</div>
);
@ -235,12 +253,12 @@ function MultisigAccountCard({
</td>
</tr>
))}
<tr>
<td>Status</td>
<td className="text-lg-right">
{info.isInitialized ? "Initialized" : "Uninitialized"}
</td>
</tr>
{!info.isInitialized && (
<tr>
<td>Status</td>
<td className="text-lg-right">Uninitialized</td>
</tr>
)}
</TableCardBody>
</div>
);

View File

@ -233,7 +233,7 @@ function TokenTransactionRow({
</td>
<td>
<Address pubkey={mint} link />
<Address pubkey={mint} link truncate />
</td>
<td>{typeName}</td>

View File

@ -9,6 +9,8 @@ import {
useFetchTokenLargestAccounts,
} from "providers/mints/largest";
import { FetchStatus } from "providers/cache";
import { TokenRegistry } from "tokenRegistry";
import { useCluster } from "providers/cluster";
export function TokenLargestAccountsCard({ pubkey }: { pubkey: PublicKey }) {
const mintAddress = pubkey.toBase58();
@ -16,6 +18,9 @@ export function TokenLargestAccountsCard({ pubkey }: { pubkey: PublicKey }) {
const largestAccounts = useTokenLargestTokens(mintAddress);
const fetchLargestAccounts = useFetchTokenLargestAccounts();
const refreshLargest = () => fetchLargestAccounts(pubkey);
const { cluster } = useCluster();
const unit = TokenRegistry.get(mintAddress, cluster)?.symbol;
const unitLabel = unit ? `(${unit})` : "";
React.useEffect(() => {
if (!largestAccounts) refreshLargest();
@ -61,7 +66,7 @@ export function TokenLargestAccountsCard({ pubkey }: { pubkey: PublicKey }) {
<tr>
<th className="text-muted">Rank</th>
<th className="text-muted">Address</th>
<th className="text-muted text-right">Balance</th>
<th className="text-muted text-right">Balance {unitLabel}</th>
<th className="text-muted text-right">% of Total Supply</th>
</tr>
</thead>

View File

@ -4,6 +4,7 @@ import { PublicKey } from "@solana/web3.js";
import { clusterPath } from "utils/url";
import { displayAddress } from "utils/tx";
import { Pubkey } from "solana-sdk-wasm";
import { useCluster } from "providers/cluster";
type CopyState = "copy" | "copied";
type Props = {
@ -11,11 +12,13 @@ type Props = {
alignRight?: boolean;
link?: boolean;
raw?: boolean;
truncate?: boolean;
};
export function Address({ pubkey, alignRight, link, raw }: Props) {
export function Address({ pubkey, alignRight, link, raw, truncate }: Props) {
const [state, setState] = useState<CopyState>("copy");
const address = pubkey.toBase58();
const { cluster } = useCluster();
const copyToClipboard = () => navigator.clipboard.writeText(address);
const handleClick = () =>
@ -36,14 +39,16 @@ export function Address({ pubkey, alignRight, link, raw }: Props) {
<span className="c-pointer font-size-tiny mr-2">{copyIcon}</span>
<span className="text-monospace">
{link ? (
<Link className="" to={clusterPath(`/address/${address}`)}>
{raw ? address : displayAddress(address)}
<span className="fe fe-external-link ml-2"></span>
<Link
className={truncate ? "text-truncate address-truncate" : ""}
to={clusterPath(`/address/${address}`)}
>
{raw ? address : displayAddress(address, cluster)}
</Link>
) : raw ? (
address
) : (
displayAddress(address)
<span className={truncate ? "text-truncate address-truncate" : ""}>
{raw ? address : displayAddress(address, cluster)}
</span>
)}
</span>
</>

View File

@ -42,7 +42,6 @@ export function Signature({ signature, alignRight, link }: Props) {
{link ? (
<Link className="" to={clusterPath(`/tx/${signature}`)}>
{signature}
<span className="fe fe-external-link ml-2"></span>
</Link>
) : (
signature

View File

@ -18,6 +18,7 @@ import { OwnedTokensCard } from "components/account/OwnedTokensCard";
import { TransactionHistoryCard } from "components/account/TransactionHistoryCard";
import { TokenHistoryCard } from "components/account/TokenHistoryCard";
import { TokenLargestAccountsCard } from "components/account/TokenLargestAccountsCard";
import { TokenRegistry } from "tokenRegistry";
type Props = { address: string; tab?: string };
export function AccountDetailsPage({ address, tab }: Props) {
@ -31,8 +32,7 @@ export function AccountDetailsPage({ address, tab }: Props) {
<div className="container mt-n3">
<div className="header">
<div className="header-body">
<h6 className="header-pretitle">Details</h6>
<h4 className="header-title">Account</h4>
<AccountHeader address={address} />
</div>
</div>
{!pubkey ? (
@ -44,6 +44,38 @@ export function AccountDetailsPage({ address, tab }: Props) {
);
}
export function AccountHeader({ address }: { address: string }) {
const { cluster } = useCluster();
const tokenDetails = TokenRegistry.get(address, cluster);
if (tokenDetails) {
return (
<div className="row align-items-end">
<div className="col-auto">
<div className="avatar avatar-lg header-avatar-top">
<img
src={tokenDetails.logo}
alt="token logo"
className="avatar-img rounded-circle border border-4 border-body"
/>
</div>
</div>
<div className="col mb-3 ml-n3 ml-md-n2">
<h6 className="header-pretitle">Token</h6>
<h2 className="header-title">{tokenDetails.name}</h2>
</div>
</div>
);
}
return (
<>
<h6 className="header-pretitle">Details</h6>
<h2 className="header-title">Account</h2>
</>
);
}
function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
const fetchAccount = useFetchAccountInfo();
const address = pubkey.toBase58();
@ -79,9 +111,9 @@ function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
if (data && data?.name === "spl-token") {
if (data.parsed.type === "mint") {
tabs.push({
slug: "holders",
title: "Holders",
path: "/holders",
slug: "largest",
title: "Distribution",
path: "/largest",
});
}
} else {
@ -139,7 +171,7 @@ type Tab = {
path: string;
};
type MoreTabs = "history" | "tokens" | "holders";
type MoreTabs = "history" | "tokens" | "largest";
function MoreSection({
account,
tab,
@ -180,7 +212,7 @@ function MoreSection({
</>
)}
{tab === "history" && <TransactionHistoryCard pubkey={pubkey} />}
{tab === "holders" && <TokenLargestAccountsCard pubkey={pubkey} />}
{tab === "largest" && <TokenLargestAccountsCard pubkey={pubkey} />}
</>
);
}

View File

@ -28,4 +28,5 @@ $input-group-addon-color: $white !default;
$theme-colors: (
"black": $black,
"gray": $gray-600,
"gray-dark": $gray-800-dark,
);

View File

@ -173,3 +173,20 @@ h4.slot-pill {
.search-bar__menu {
border-radius: $border-radius !important;
}
.token-icon {
width: 24px;
height: 24px;
}
.address-truncate {
@include media-breakpoint-down(md) {
max-width: 180px;
display: inline-block;
vertical-align: bottom;
}
@include media-breakpoint-down(sm) {
max-width: 120px;
}
}

View File

@ -0,0 +1,38 @@
import { Cluster } from "providers/cluster";
export type TokenDetails = {
name: string;
symbol: string;
logo: string;
icon: string;
website: string;
};
const ENABLE_DETAILS = !!new URLSearchParams(window.location.search).get(
"test"
);
function get(address: string, cluster: Cluster): TokenDetails | undefined {
if (ENABLE_DETAILS && cluster === Cluster.MainnetBeta)
return MAINNET_TOKENS[address];
}
function all(cluster: Cluster) {
if (ENABLE_DETAILS && cluster === Cluster.MainnetBeta) return MAINNET_TOKENS;
return {};
}
export const TokenRegistry = {
get,
all,
};
const MAINNET_TOKENS: { [key: string]: TokenDetails } = {
MSRMmR98uWsTBgusjwyNkE8nDtV79sJznTedhJLzS4B: {
name: "MegaSerum",
symbol: "MSRM",
logo: "/tokens/serum-64.png",
icon: "/tokens/serum-32.png",
website: "https://projectserum.com",
},
};

View File

@ -12,17 +12,30 @@ import {
TransactionInstruction,
Transaction,
} from "@solana/web3.js";
import { TokenRegistry } from "tokenRegistry";
import { Cluster } from "providers/cluster";
export const PROGRAM_IDS = {
Budget1111111111111111111111111111111111111: "Budget",
Config1111111111111111111111111111111111111: "Config",
Exchange11111111111111111111111111111111111: "Exchange",
[StakeProgram.programId.toBase58()]: "Stake",
Storage111111111111111111111111111111111111: "Storage",
[SystemProgram.programId.toBase58()]: "System",
Vest111111111111111111111111111111111111111: "Vest",
[VOTE_PROGRAM_ID.toBase58()]: "Vote",
TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o: "Token",
export type ProgramName =
| "Budget Program"
| "Config Program"
| "Exchange Program"
| "Stake Program"
| "Storage Program"
| "System Program"
| "Vest Program"
| "Vote Program"
| "SPL Token";
export const PROGRAM_IDS: { [key: string]: ProgramName } = {
Budget1111111111111111111111111111111111111: "Budget Program",
Config1111111111111111111111111111111111111: "Config Program",
Exchange11111111111111111111111111111111111: "Exchange Program",
[StakeProgram.programId.toBase58()]: "Stake Program",
Storage111111111111111111111111111111111111: "Storage Program",
[SystemProgram.programId.toBase58()]: "System Program",
Vest111111111111111111111111111111111111111: "Vest Program",
[VOTE_PROGRAM_ID.toBase58()]: "Vote Program",
TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o: "SPL Token",
};
const LOADER_IDS = {
@ -48,12 +61,13 @@ export const SYSVAR_IDS = {
[SYSVAR_STAKE_HISTORY_PUBKEY.toBase58()]: "SYSVAR_STAKE_HISTORY",
};
export function displayAddress(address: string): string {
export function displayAddress(address: string, cluster: Cluster): string {
return (
PROGRAM_IDS[address] ||
LOADER_IDS[address] ||
SYSVAR_IDS[address] ||
SYSVAR_ID[address] ||
TokenRegistry.get(address, cluster)?.name ||
address
);
}

View File

@ -22,14 +22,11 @@ export const TokenAccountInfo = pick({
isNative: boolean(),
mint: Pubkey,
owner: Pubkey,
amount: optional(number()), // TODO remove when ui amount is deployed
tokenAmount: optional(
object({
decimals: number(),
uiAmount: number(),
amount: string(),
})
),
tokenAmount: pick({
decimals: number(),
uiAmount: number(),
amount: string(),
}),
delegate: nullable(optional(Pubkey)),
delegatedAmount: optional(number()),
});