Add supply stats page
This commit is contained in:
parent
2312658492
commit
406f35edfa
|
@ -1269,9 +1269,9 @@
|
|||
"integrity": "sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw=="
|
||||
},
|
||||
"@solana/web3.js": {
|
||||
"version": "0.52.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.52.1.tgz",
|
||||
"integrity": "sha512-xv8PknS9sjnMxPXaCZEb8Yk+S0O3lScw91NJyYjnIPYxYnxJ96H7BUCDh1zOj5op4nT8u/n4wRYG+YWT/XRwiA==",
|
||||
"version": "0.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.54.0.tgz",
|
||||
"integrity": "sha512-tpP8PQRPI/pG2pruEOB2La7Oc1xWX3NfiQCcuh7ABkAznVFsWomoWigIaHcxPn5HJZytkhDctTpGjEEiwB0dQQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"bn.js": "^5.0.0",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "^0.52.1",
|
||||
"@solana/web3.js": "^0.54.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
|
|
|
@ -10,6 +10,7 @@ import Logo from "./img/logos-solana/light-explorer-logo.svg";
|
|||
import { TX_ALIASES } from "./providers/transactions";
|
||||
import { ACCOUNT_ALIASES, ACCOUNT_ALIASES_PLURAL } from "./providers/accounts";
|
||||
import TabbedPage from "components/TabbedPage";
|
||||
import SupplyCard from "components/SupplyCard";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
@ -29,6 +30,11 @@ function App() {
|
|||
</nav>
|
||||
|
||||
<Switch>
|
||||
<Route exact path="/supply">
|
||||
<TabbedPage tab="Supply">
|
||||
<SupplyCard />
|
||||
</TabbedPage>
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={TX_ALIASES.flatMap(tx => [tx, tx + "s"]).map(
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import React from "react";
|
||||
import { useSupply, useFetchSupply } from "providers/supply";
|
||||
import LoadingCard from "./common/LoadingCard";
|
||||
import ErrorCard from "./common/ErrorCard";
|
||||
import { lamportsToSolString } from "utils";
|
||||
import TableCardBody from "./common/TableCardBody";
|
||||
|
||||
export default function SupplyCard() {
|
||||
const supply = useSupply();
|
||||
const fetchSupply = useFetchSupply();
|
||||
|
||||
if (typeof supply === "boolean") {
|
||||
if (supply) return <LoadingCard />;
|
||||
return <ErrorCard text="Not connected to the cluster" />;
|
||||
}
|
||||
|
||||
if (typeof supply === "string") {
|
||||
return <ErrorCard text={supply} retry={fetchSupply} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
{renderHeader()}
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td className="w-100">Total Supply (SOL)</td>
|
||||
<td>{lamportsToSolString(supply.total)}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="w-100">Circulating Supply (SOL)</td>
|
||||
<td>{lamportsToSolString(supply.circulating)}</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderHeader = () => {
|
||||
return (
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h4 className="card-header-title">Supply Stats</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
|
|||
import { useClusterModal } from "providers/cluster";
|
||||
import ClusterStatusButton from "components/ClusterStatusButton";
|
||||
|
||||
export type Tab = "Transactions" | "Accounts";
|
||||
export type Tab = "Transactions" | "Accounts" | "Supply";
|
||||
|
||||
type Props = { children: React.ReactNode; tab: Tab };
|
||||
export default function TabbedPage({ children, tab }: Props) {
|
||||
|
@ -31,6 +31,9 @@ export default function TabbedPage({ children, tab }: Props) {
|
|||
<li className="nav-item">
|
||||
<NavLink href="/accounts" tab="Accounts" current={tab} />
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink href="/supply" tab="Supply" current={tab} />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col-auto d-none d-md-block">
|
||||
|
|
|
@ -5,17 +5,20 @@ import "./scss/theme.scss";
|
|||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { ClusterProvider } from "./providers/cluster";
|
||||
import { SupplyProvider } from "./providers/supply";
|
||||
import { TransactionsProvider } from "./providers/transactions";
|
||||
import { AccountsProvider } from "./providers/accounts";
|
||||
|
||||
ReactDOM.render(
|
||||
<Router>
|
||||
<ClusterProvider>
|
||||
<AccountsProvider>
|
||||
<TransactionsProvider>
|
||||
<App />
|
||||
</TransactionsProvider>
|
||||
</AccountsProvider>
|
||||
<SupplyProvider>
|
||||
<AccountsProvider>
|
||||
<TransactionsProvider>
|
||||
<App />
|
||||
</TransactionsProvider>
|
||||
</AccountsProvider>
|
||||
</SupplyProvider>
|
||||
</ClusterProvider>
|
||||
</Router>,
|
||||
document.getElementById("root")
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import React from "react";
|
||||
|
||||
import { Supply, Connection } from "@solana/web3.js";
|
||||
import { useCluster, ClusterStatus } from "./cluster";
|
||||
|
||||
type State = Supply | boolean | string;
|
||||
|
||||
type Dispatch = React.Dispatch<React.SetStateAction<State>>;
|
||||
const StateContext = React.createContext<State | undefined>(undefined);
|
||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||
|
||||
type Props = { children: React.ReactNode };
|
||||
export function SupplyProvider({ children }: Props) {
|
||||
const [state, setState] = React.useState<State>(false);
|
||||
const { status, url } = useCluster();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (status === ClusterStatus.Connecting) setState(false);
|
||||
if (status === ClusterStatus.Connected) fetch(setState, url);
|
||||
}, [status, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
<DispatchContext.Provider value={setState}>
|
||||
{children}
|
||||
</DispatchContext.Provider>
|
||||
</StateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
async function fetch(dispatch: Dispatch, url: string) {
|
||||
dispatch(true);
|
||||
try {
|
||||
const connection = new Connection(url, "max");
|
||||
const supply = (await connection.getSupply()).value;
|
||||
|
||||
// Update state if selected cluster hasn't changed
|
||||
dispatch(state => {
|
||||
if (!state) return state;
|
||||
return supply;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch", err);
|
||||
dispatch("Failed to fetch supply");
|
||||
}
|
||||
}
|
||||
|
||||
export function useSupply() {
|
||||
const state = React.useContext(StateContext);
|
||||
if (state === undefined) {
|
||||
throw new Error(`useSupply must be used within a SupplyProvider`);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export function useFetchSupply() {
|
||||
const dispatch = React.useContext(DispatchContext);
|
||||
if (!dispatch) {
|
||||
throw new Error(`useFetchSupply must be used within a SupplyProvider`);
|
||||
}
|
||||
|
||||
const { url } = useCluster();
|
||||
return () => fetch(dispatch, url);
|
||||
}
|
|
@ -4,6 +4,12 @@ export function assertUnreachable(x: never): never {
|
|||
throw new Error("Unreachable!");
|
||||
}
|
||||
|
||||
export function lamportsToSolString(lamports: number): string {
|
||||
return `◎${(1.0 * Math.abs(lamports)) / LAMPORTS_PER_SOL}`;
|
||||
export function lamportsToSolString(
|
||||
lamports: number,
|
||||
maximumFractionDigits: number = 9
|
||||
): string {
|
||||
const sol = Math.abs(lamports) / LAMPORTS_PER_SOL;
|
||||
return (
|
||||
"◎" + new Intl.NumberFormat("en-US", { maximumFractionDigits }).format(sol)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue