Add token supply info to mint account page (#11584)
This commit is contained in:
@ -10,6 +10,8 @@ import { coerce } from "superstruct";
|
|||||||
import { TableCardBody } from "components/common/TableCardBody";
|
import { TableCardBody } from "components/common/TableCardBody";
|
||||||
import { Address } from "components/common/Address";
|
import { Address } from "components/common/Address";
|
||||||
import { UnknownAccountCard } from "./UnknownAccountCard";
|
import { UnknownAccountCard } from "./UnknownAccountCard";
|
||||||
|
import { useFetchTokenSupply, useTokenSupply } from "providers/mints/supply";
|
||||||
|
import { FetchStatus } from "providers/cache";
|
||||||
|
|
||||||
export function TokenAccountSection({
|
export function TokenAccountSection({
|
||||||
account,
|
account,
|
||||||
@ -44,7 +46,36 @@ function MintAccountCard({
|
|||||||
account: Account;
|
account: Account;
|
||||||
info: MintAccountInfo;
|
info: MintAccountInfo;
|
||||||
}) {
|
}) {
|
||||||
const refresh = useFetchAccountInfo();
|
const mintAddress = account.pubkey.toBase58();
|
||||||
|
const fetchInfo = useFetchAccountInfo();
|
||||||
|
const supply = useTokenSupply(mintAddress);
|
||||||
|
const fetchSupply = useFetchTokenSupply();
|
||||||
|
const refreshSupply = () => fetchSupply(account.pubkey);
|
||||||
|
const refresh = () => {
|
||||||
|
fetchInfo(account.pubkey);
|
||||||
|
refreshSupply();
|
||||||
|
};
|
||||||
|
|
||||||
|
let renderSupply;
|
||||||
|
const supplyTotal = supply?.data?.uiAmount;
|
||||||
|
if (supplyTotal === undefined) {
|
||||||
|
if (!supply || supply?.status === FetchStatus.Fetching) {
|
||||||
|
renderSupply = (
|
||||||
|
<>
|
||||||
|
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||||
|
Loading
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
renderSupply = "Fetch failed";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderSupply = supplyTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!supply) refreshSupply();
|
||||||
|
}, [mintAddress]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
@ -52,10 +83,7 @@ function MintAccountCard({
|
|||||||
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||||
Token Mint Account
|
Token Mint Account
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button className="btn btn-white btn-sm" onClick={refresh}>
|
||||||
className="btn btn-white btn-sm"
|
|
||||||
onClick={() => refresh(account.pubkey)}
|
|
||||||
>
|
|
||||||
<span className="fe fe-refresh-cw mr-2"></span>
|
<span className="fe fe-refresh-cw mr-2"></span>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
@ -68,16 +96,20 @@ function MintAccountCard({
|
|||||||
<Address pubkey={account.pubkey} alignRight raw />
|
<Address pubkey={account.pubkey} alignRight raw />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Supply</td>
|
||||||
|
<td className="text-lg-right">{renderSupply}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Decimals</td>
|
<td>Decimals</td>
|
||||||
<td className="text-lg-right">{info.decimals}</td>
|
<td className="text-lg-right">{info.decimals}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
{!info.isInitialized && (
|
||||||
<td>Status</td>
|
<tr>
|
||||||
<td className="text-lg-right">
|
<td>Status</td>
|
||||||
{info.isInitialized ? "Initialized" : "Uninitialized"}
|
<td className="text-lg-right">Uninitialized</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
)}
|
||||||
{info.owner !== undefined && (
|
{info.owner !== undefined && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Owner</td>
|
<td>Owner</td>
|
||||||
|
96
explorer/src/components/account/TokenLargestAccountsCard.tsx
Normal file
96
explorer/src/components/account/TokenLargestAccountsCard.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { PublicKey, TokenAccountBalancePair } from "@solana/web3.js";
|
||||||
|
import { LoadingCard } from "components/common/LoadingCard";
|
||||||
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import { useTokenSupply } from "providers/mints/supply";
|
||||||
|
import {
|
||||||
|
useTokenLargestTokens,
|
||||||
|
useFetchTokenLargestAccounts,
|
||||||
|
} from "providers/mints/largest";
|
||||||
|
import { FetchStatus } from "providers/cache";
|
||||||
|
|
||||||
|
export function TokenLargestAccountsCard({ pubkey }: { pubkey: PublicKey }) {
|
||||||
|
const mintAddress = pubkey.toBase58();
|
||||||
|
const supply = useTokenSupply(mintAddress);
|
||||||
|
const largestAccounts = useTokenLargestTokens(mintAddress);
|
||||||
|
const fetchLargestAccounts = useFetchTokenLargestAccounts();
|
||||||
|
const refreshLargest = () => fetchLargestAccounts(pubkey);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!largestAccounts) refreshLargest();
|
||||||
|
}, [mintAddress]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const supplyTotal = supply?.data?.uiAmount;
|
||||||
|
if (!supplyTotal || !largestAccounts) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (largestAccounts?.data === undefined) {
|
||||||
|
if (largestAccounts.status === FetchStatus.Fetching) {
|
||||||
|
return <LoadingCard message="Loading largest accounts" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorCard
|
||||||
|
retry={refreshLargest}
|
||||||
|
text="Failed to fetch largest accounts"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = largestAccounts.data.largest;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header">
|
||||||
|
<div className="row align-items-center">
|
||||||
|
<div className="col">
|
||||||
|
<h4 className="card-header-title">Largest Accounts</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="table-responsive mb-0">
|
||||||
|
<table className="table table-sm table-nowrap card-table">
|
||||||
|
<thead>
|
||||||
|
<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">% of Total Supply</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="list">
|
||||||
|
{accounts.map((account, index) =>
|
||||||
|
renderAccountRow(account, index, supplyTotal)
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderAccountRow = (
|
||||||
|
account: TokenAccountBalancePair,
|
||||||
|
index: number,
|
||||||
|
supply: number
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>
|
||||||
|
<span className="badge badge-soft-gray badge-pill">{index + 1}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Address pubkey={account.address} link />
|
||||||
|
</td>
|
||||||
|
<td className="text-right">{account.uiAmount}</td>
|
||||||
|
<td className="text-right">{`${(
|
||||||
|
(100 * account.uiAmount) /
|
||||||
|
supply
|
||||||
|
).toFixed(3)}%`}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
@ -10,6 +10,7 @@ import { SupplyProvider } from "./providers/supply";
|
|||||||
import { TransactionsProvider } from "./providers/transactions";
|
import { TransactionsProvider } from "./providers/transactions";
|
||||||
import { AccountsProvider } from "./providers/accounts";
|
import { AccountsProvider } from "./providers/accounts";
|
||||||
import { StatsProvider } from "providers/stats";
|
import { StatsProvider } from "providers/stats";
|
||||||
|
import { MintsProvider } from "providers/mints";
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Router>
|
<Router>
|
||||||
@ -18,9 +19,11 @@ ReactDOM.render(
|
|||||||
<SupplyProvider>
|
<SupplyProvider>
|
||||||
<RichListProvider>
|
<RichListProvider>
|
||||||
<AccountsProvider>
|
<AccountsProvider>
|
||||||
<TransactionsProvider>
|
<MintsProvider>
|
||||||
<App />
|
<TransactionsProvider>
|
||||||
</TransactionsProvider>
|
<App />
|
||||||
|
</TransactionsProvider>
|
||||||
|
</MintsProvider>
|
||||||
</AccountsProvider>
|
</AccountsProvider>
|
||||||
</RichListProvider>
|
</RichListProvider>
|
||||||
</SupplyProvider>
|
</SupplyProvider>
|
||||||
|
@ -1,33 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import { FetchStatus } from "providers/cache";
|
import { FetchStatus } from "providers/cache";
|
||||||
import { useFetchAccountInfo, useAccountInfo } from "providers/accounts";
|
import {
|
||||||
|
useFetchAccountInfo,
|
||||||
|
useAccountInfo,
|
||||||
|
Account,
|
||||||
|
} from "providers/accounts";
|
||||||
import { StakeAccountSection } from "components/account/StakeAccountSection";
|
import { StakeAccountSection } from "components/account/StakeAccountSection";
|
||||||
import { TokenAccountSection } from "components/account/TokenAccountSection";
|
import { TokenAccountSection } from "components/account/TokenAccountSection";
|
||||||
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 { useCluster, ClusterStatus } from "providers/cluster";
|
import { useCluster, ClusterStatus } from "providers/cluster";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink, Redirect, useLocation } from "react-router-dom";
|
||||||
import { clusterPath } from "utils/url";
|
import { clusterPath } from "utils/url";
|
||||||
import { UnknownAccountCard } from "components/account/UnknownAccountCard";
|
import { UnknownAccountCard } from "components/account/UnknownAccountCard";
|
||||||
import { OwnedTokensCard } from "components/account/OwnedTokensCard";
|
import { OwnedTokensCard } from "components/account/OwnedTokensCard";
|
||||||
import { TransactionHistoryCard } from "components/account/TransactionHistoryCard";
|
import { TransactionHistoryCard } from "components/account/TransactionHistoryCard";
|
||||||
import { TokenHistoryCard } from "components/account/TokenHistoryCard";
|
import { TokenHistoryCard } from "components/account/TokenHistoryCard";
|
||||||
|
import { TokenLargestAccountsCard } from "components/account/TokenLargestAccountsCard";
|
||||||
|
|
||||||
type Props = { address: string; tab?: string };
|
type Props = { address: string; tab?: string };
|
||||||
export function AccountDetailsPage({ address, tab }: Props) {
|
export function AccountDetailsPage({ address, tab }: Props) {
|
||||||
let pubkey: PublicKey | undefined;
|
let pubkey: PublicKey | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pubkey = new PublicKey(address);
|
pubkey = new PublicKey(address);
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
console.error(err);
|
|
||||||
// TODO handle bad addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
let moreTab: MoreTabs = "history";
|
|
||||||
if (tab === "history" || tab === "tokens") {
|
|
||||||
moreTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mt-n3">
|
<div className="container mt-n3">
|
||||||
@ -37,18 +35,21 @@ export function AccountDetailsPage({ address, tab }: Props) {
|
|||||||
<h4 className="header-title">Account</h4>
|
<h4 className="header-title">Account</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{pubkey && <InfoSection pubkey={pubkey} />}
|
{!pubkey ? (
|
||||||
{pubkey && <MoreSection pubkey={pubkey} tab={moreTab} />}
|
<ErrorCard text={`Address "${address}" is not valid`} />
|
||||||
|
) : (
|
||||||
|
<DetailsSections pubkey={pubkey} tab={tab} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfoSection({ pubkey }: { pubkey: PublicKey }) {
|
function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
|
||||||
const fetchAccount = useFetchAccountInfo();
|
const fetchAccount = useFetchAccountInfo();
|
||||||
const address = pubkey.toBase58();
|
const address = pubkey.toBase58();
|
||||||
const info = useAccountInfo(address);
|
const info = useAccountInfo(address);
|
||||||
const refresh = useFetchAccountInfo();
|
|
||||||
const { status } = useCluster();
|
const { status } = useCluster();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
// Fetch account on load
|
// Fetch account on load
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -61,11 +62,53 @@ function InfoSection({ pubkey }: { pubkey: PublicKey }) {
|
|||||||
info.status === FetchStatus.FetchFailed ||
|
info.status === FetchStatus.FetchFailed ||
|
||||||
info.data?.lamports === undefined
|
info.data?.lamports === undefined
|
||||||
) {
|
) {
|
||||||
return <ErrorCard retry={() => refresh(pubkey)} text="Fetch Failed" />;
|
return <ErrorCard retry={() => fetchAccount(pubkey)} text="Fetch Failed" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = info.data;
|
const account = info.data;
|
||||||
const data = account?.details?.data;
|
const data = account?.details?.data;
|
||||||
|
|
||||||
|
let tabs: Tab[] = [
|
||||||
|
{
|
||||||
|
slug: "history",
|
||||||
|
title: "History",
|
||||||
|
path: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (data && data?.name === "spl-token") {
|
||||||
|
if (data.parsed.type === "mint") {
|
||||||
|
tabs.push({
|
||||||
|
slug: "holders",
|
||||||
|
title: "Holders",
|
||||||
|
path: "/holders",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tabs.push({
|
||||||
|
slug: "tokens",
|
||||||
|
title: "Tokens",
|
||||||
|
path: "/tokens",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let moreTab: MoreTabs = "history";
|
||||||
|
if (tab && tabs.filter(({ slug }) => slug === tab).length === 0) {
|
||||||
|
return <Redirect to={{ ...location, pathname: `/address/${address}` }} />;
|
||||||
|
} else if (tab) {
|
||||||
|
moreTab = tab as MoreTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{<InfoSection account={account} />}
|
||||||
|
{<MoreSection account={account} tab={moreTab} tabs={tabs} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InfoSection({ account }: { account: Account }) {
|
||||||
|
const data = account?.details?.data;
|
||||||
if (data && data.name === "stake") {
|
if (data && data.name === "stake") {
|
||||||
let stakeAccountType, stakeAccount;
|
let stakeAccountType, stakeAccount;
|
||||||
if ("accountType" in data.parsed) {
|
if ("accountType" in data.parsed) {
|
||||||
@ -90,11 +133,24 @@ function InfoSection({ pubkey }: { pubkey: PublicKey }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MoreTabs = "history" | "tokens";
|
type Tab = {
|
||||||
function MoreSection({ pubkey, tab }: { pubkey: PublicKey; tab: MoreTabs }) {
|
slug: MoreTabs;
|
||||||
const address = pubkey.toBase58();
|
title: string;
|
||||||
const info = useAccountInfo(address);
|
path: string;
|
||||||
if (info?.data === undefined) return null;
|
};
|
||||||
|
|
||||||
|
type MoreTabs = "history" | "tokens" | "holders";
|
||||||
|
function MoreSection({
|
||||||
|
account,
|
||||||
|
tab,
|
||||||
|
tabs,
|
||||||
|
}: {
|
||||||
|
account: Account;
|
||||||
|
tab: MoreTabs;
|
||||||
|
tabs: Tab[];
|
||||||
|
}) {
|
||||||
|
const pubkey = account.pubkey;
|
||||||
|
const address = account.pubkey.toBase58();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -102,24 +158,17 @@ function MoreSection({ pubkey, tab }: { pubkey: PublicKey; tab: MoreTabs }) {
|
|||||||
<div className="header">
|
<div className="header">
|
||||||
<div className="header-body pt-0">
|
<div className="header-body pt-0">
|
||||||
<ul className="nav nav-tabs nav-overflow header-tabs">
|
<ul className="nav nav-tabs nav-overflow header-tabs">
|
||||||
<li className="nav-item">
|
{tabs.map(({ title, path }) => (
|
||||||
<NavLink
|
<li className="nav-item">
|
||||||
className="nav-link"
|
<NavLink
|
||||||
to={clusterPath(`/address/${address}`)}
|
className="nav-link"
|
||||||
exact
|
to={clusterPath(`/address/${address}${path}`)}
|
||||||
>
|
exact
|
||||||
History
|
>
|
||||||
</NavLink>
|
{title}
|
||||||
</li>
|
</NavLink>
|
||||||
<li className="nav-item">
|
</li>
|
||||||
<NavLink
|
))}
|
||||||
className="nav-link"
|
|
||||||
to={clusterPath(`/address/${address}/tokens`)}
|
|
||||||
exact
|
|
||||||
>
|
|
||||||
Tokens
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -131,6 +180,7 @@ function MoreSection({ pubkey, tab }: { pubkey: PublicKey; tab: MoreTabs }) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{tab === "history" && <TransactionHistoryCard pubkey={pubkey} />}
|
{tab === "history" && <TransactionHistoryCard pubkey={pubkey} />}
|
||||||
|
{tab === "holders" && <TokenLargestAccountsCard pubkey={pubkey} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
12
explorer/src/providers/mints/index.tsx
Normal file
12
explorer/src/providers/mints/index.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { SupplyProvider } from "./supply";
|
||||||
|
import { LargestAccountsProvider } from "./largest";
|
||||||
|
|
||||||
|
type ProviderProps = { children: React.ReactNode };
|
||||||
|
export function MintsProvider({ children }: ProviderProps) {
|
||||||
|
return (
|
||||||
|
<SupplyProvider>
|
||||||
|
<LargestAccountsProvider>{children}</LargestAccountsProvider>
|
||||||
|
</SupplyProvider>
|
||||||
|
);
|
||||||
|
}
|
99
explorer/src/providers/mints/largest.tsx
Normal file
99
explorer/src/providers/mints/largest.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useCluster } from "providers/cluster";
|
||||||
|
import * as Cache from "providers/cache";
|
||||||
|
import { ActionType, FetchStatus } from "providers/cache";
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
Connection,
|
||||||
|
TokenAccountBalancePair,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
|
type LargestAccounts = {
|
||||||
|
largest: TokenAccountBalancePair[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = Cache.State<LargestAccounts>;
|
||||||
|
type Dispatch = Cache.Dispatch<LargestAccounts>;
|
||||||
|
|
||||||
|
const StateContext = React.createContext<State | undefined>(undefined);
|
||||||
|
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||||
|
|
||||||
|
type ProviderProps = { children: React.ReactNode };
|
||||||
|
export function LargestAccountsProvider({ children }: ProviderProps) {
|
||||||
|
const { url } = useCluster();
|
||||||
|
const [state, dispatch] = Cache.useReducer<LargestAccounts>(url);
|
||||||
|
|
||||||
|
// Clear cache whenever cluster is changed
|
||||||
|
React.useEffect(() => {
|
||||||
|
dispatch({ type: ActionType.Clear, url });
|
||||||
|
}, [dispatch, url]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StateContext.Provider value={state}>
|
||||||
|
<DispatchContext.Provider value={dispatch}>
|
||||||
|
{children}
|
||||||
|
</DispatchContext.Provider>
|
||||||
|
</StateContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLargestAccounts(
|
||||||
|
dispatch: Dispatch,
|
||||||
|
pubkey: PublicKey,
|
||||||
|
url: string
|
||||||
|
) {
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.Update,
|
||||||
|
key: pubkey.toBase58(),
|
||||||
|
status: Cache.FetchStatus.Fetching,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
|
||||||
|
let data;
|
||||||
|
let fetchStatus;
|
||||||
|
try {
|
||||||
|
data = {
|
||||||
|
largest: (
|
||||||
|
await new Connection(url, "single").getTokenLargestAccounts(pubkey)
|
||||||
|
).value,
|
||||||
|
};
|
||||||
|
fetchStatus = FetchStatus.Fetched;
|
||||||
|
} catch (error) {
|
||||||
|
fetchStatus = FetchStatus.FetchFailed;
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.Update,
|
||||||
|
status: fetchStatus,
|
||||||
|
data,
|
||||||
|
key: pubkey.toBase58(),
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFetchTokenLargestAccounts() {
|
||||||
|
const dispatch = React.useContext(DispatchContext);
|
||||||
|
if (!dispatch) {
|
||||||
|
throw new Error(
|
||||||
|
`useFetchTokenLargestAccounts must be used within a MintsProvider`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url } = useCluster();
|
||||||
|
return (pubkey: PublicKey) => {
|
||||||
|
fetchLargestAccounts(dispatch, pubkey, url);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTokenLargestTokens(
|
||||||
|
address: string
|
||||||
|
): Cache.CacheEntry<LargestAccounts> | undefined {
|
||||||
|
const context = React.useContext(StateContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
`useTokenLargestTokens must be used within a MintsProvider`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.entries[address];
|
||||||
|
}
|
80
explorer/src/providers/mints/supply.tsx
Normal file
80
explorer/src/providers/mints/supply.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useCluster } from "providers/cluster";
|
||||||
|
import * as Cache from "providers/cache";
|
||||||
|
import { ActionType, FetchStatus } from "providers/cache";
|
||||||
|
import { TokenAmount, PublicKey, Connection } from "@solana/web3.js";
|
||||||
|
|
||||||
|
type State = Cache.State<TokenAmount>;
|
||||||
|
type Dispatch = Cache.Dispatch<TokenAmount>;
|
||||||
|
|
||||||
|
const StateContext = React.createContext<State | undefined>(undefined);
|
||||||
|
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||||
|
|
||||||
|
type ProviderProps = { children: React.ReactNode };
|
||||||
|
export function SupplyProvider({ children }: ProviderProps) {
|
||||||
|
const { url } = useCluster();
|
||||||
|
const [state, dispatch] = Cache.useReducer<TokenAmount>(url);
|
||||||
|
|
||||||
|
// Clear cache whenever cluster is changed
|
||||||
|
React.useEffect(() => {
|
||||||
|
dispatch({ type: ActionType.Clear, url });
|
||||||
|
}, [dispatch, url]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StateContext.Provider value={state}>
|
||||||
|
<DispatchContext.Provider value={dispatch}>
|
||||||
|
{children}
|
||||||
|
</DispatchContext.Provider>
|
||||||
|
</StateContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSupply(dispatch: Dispatch, pubkey: PublicKey, url: string) {
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.Update,
|
||||||
|
key: pubkey.toBase58(),
|
||||||
|
status: Cache.FetchStatus.Fetching,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
|
||||||
|
let data;
|
||||||
|
let fetchStatus;
|
||||||
|
try {
|
||||||
|
data = (await new Connection(url, "single").getTokenSupply(pubkey)).value;
|
||||||
|
|
||||||
|
fetchStatus = FetchStatus.Fetched;
|
||||||
|
} catch (error) {
|
||||||
|
fetchStatus = FetchStatus.FetchFailed;
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.Update,
|
||||||
|
status: fetchStatus,
|
||||||
|
data,
|
||||||
|
key: pubkey.toBase58(),
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFetchTokenSupply() {
|
||||||
|
const dispatch = React.useContext(DispatchContext);
|
||||||
|
if (!dispatch) {
|
||||||
|
throw new Error(`useFetchTokenSupply must be used within a MintsProvider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url } = useCluster();
|
||||||
|
return (pubkey: PublicKey) => {
|
||||||
|
fetchSupply(dispatch, pubkey, url);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTokenSupply(
|
||||||
|
address: string
|
||||||
|
): Cache.CacheEntry<TokenAmount> | undefined {
|
||||||
|
const context = React.useContext(StateContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(`useTokenSupply must be used within a MintsProvider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.entries[address];
|
||||||
|
}
|
Reference in New Issue
Block a user