solana/explorer/src/providers/accounts/index.tsx

434 lines
12 KiB
TypeScript
Raw Normal View History

2020-03-31 06:58:48 -07:00
import React from "react";
import { pubkeyToString } from "utils";
import { PublicKey, Connection, StakeActivationData } from "@solana/web3.js";
import { useCluster, Cluster } from "../cluster";
2020-05-14 07:14:28 -07:00
import { HistoryProvider } from "./history";
import { TokensProvider } from "./tokens";
2021-03-12 21:31:52 -08:00
import { create } from "superstruct";
import { ParsedInfo } from "validators";
import { StakeAccount } from "validators/accounts/stake";
import {
TokenAccount,
MintAccountInfo,
TokenAccountInfo,
} from "validators/accounts/token";
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";
import { FlaggedAccountsProvider } from "./flagged-accounts";
import {
ProgramDataAccount,
ProgramDataAccountInfo,
UpgradeableLoaderAccount,
} from "validators/accounts/upgradeable-program";
import { RewardsProvider } from "./rewards";
import { programs, MetadataJson } from "@metaplex/js";
import getEditionInfo, { EditionInfo } from "./utils/getEditionInfo";
2020-05-14 07:14:28 -07:00
export { useAccountHistory } from "./history";
2020-03-31 06:58:48 -07:00
const Metadata = programs.metadata.Metadata;
export type StakeProgramData = {
program: "stake";
parsed: StakeAccount;
activation?: StakeActivationData;
};
export type UpgradeableLoaderAccountData = {
program: "bpf-upgradeable-loader";
parsed: UpgradeableLoaderAccount;
programData?: ProgramDataAccountInfo;
};
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
export type NFTData = {
metadata: programs.metadata.MetadataData;
json: MetadataJson | undefined;
editionInfo: EditionInfo;
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
};
export type TokenProgramData = {
program: "spl-token";
parsed: TokenAccount;
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
nftData?: NFTData;
};
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 =
| UpgradeableLoaderAccountData
| StakeProgramData
| TokenProgramData
| VoteProgramData
| NonceProgramData
| SysvarProgramData
| ConfigProgramData;
2020-03-31 06:58:48 -07:00
export interface Details {
executable: boolean;
owner: PublicKey;
space: number;
data?: ProgramData;
2020-03-31 06:58:48 -07:00
}
export interface Account {
pubkey: PublicKey;
lamports: number;
2020-03-31 06:58:48 -07:00
details?: Details;
}
type State = Cache.State<Account>;
type Dispatch = Cache.Dispatch<Account>;
2020-03-31 06:58:48 -07:00
const StateContext = React.createContext<State | undefined>(undefined);
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
type AccountsProviderProps = { children: React.ReactNode };
export function AccountsProvider({ children }: AccountsProviderProps) {
const { url } = useCluster();
const [state, dispatch] = Cache.useReducer<Account>(url);
2020-03-31 06:58:48 -07:00
// Clear accounts cache whenever cluster is changed
2020-03-31 06:58:48 -07:00
React.useEffect(() => {
dispatch({ type: ActionType.Clear, url });
}, [dispatch, url]);
2020-03-31 06:58:48 -07:00
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<TokensProvider>
<HistoryProvider>
<RewardsProvider>
<FlaggedAccountsProvider>{children}</FlaggedAccountsProvider>
</RewardsProvider>
</HistoryProvider>
</TokensProvider>
2020-03-31 06:58:48 -07:00
</DispatchContext.Provider>
</StateContext.Provider>
);
}
2020-05-12 03:32:14 -07:00
async function fetchAccountInfo(
2020-03-31 06:58:48 -07:00
dispatch: Dispatch,
2020-05-12 03:32:14 -07:00
pubkey: PublicKey,
cluster: Cluster,
url: string
2020-03-31 06:58:48 -07:00
) {
dispatch({
type: ActionType.Update,
key: pubkey.toBase58(),
status: Cache.FetchStatus.Fetching,
url,
2020-03-31 06:58:48 -07:00
});
let data;
2020-05-12 03:32:14 -07:00
let fetchStatus;
2020-03-31 06:58:48 -07:00
try {
2021-04-23 17:32:28 -07:00
const connection = new Connection(url, "confirmed");
const result = (await connection.getParsedAccountInfo(pubkey)).value;
let lamports, details;
2020-04-06 03:54:29 -07:00
if (result === null) {
2020-04-05 01:31:40 -07:00
lamports = 0;
} else {
2020-04-06 03:54:29 -07:00
lamports = result.lamports;
2020-05-14 00:30:33 -07:00
// Only save data in memory if we can decode it
let space: number;
if (!("parsed" in result.data)) {
space = result.data.length;
} else {
space = result.data.space;
}
let data: ProgramData | undefined;
if ("parsed" in result.data) {
try {
2021-03-12 21:31:52 -08:00
const info = create(result.data.parsed, ParsedInfo);
switch (result.data.program) {
case "bpf-upgradeable-loader": {
const parsed = create(info, UpgradeableLoaderAccount);
// Fetch program data to get program upgradeability info
let programData: ProgramDataAccountInfo | undefined;
if (parsed.type === "program") {
const result = (
await connection.getParsedAccountInfo(parsed.info.programData)
).value;
if (
result &&
"parsed" in result.data &&
result.data.program === "bpf-upgradeable-loader"
) {
const info = create(result.data.parsed, ParsedInfo);
programData = create(info, ProgramDataAccount).info;
} else {
throw new Error(
`invalid program data account for program: ${pubkey.toBase58()}`
);
}
}
data = {
program: result.data.program,
parsed,
programData,
};
break;
}
case "stake": {
2021-03-12 21:31:52 -08:00
const parsed = create(info, StakeAccount);
const isDelegated = parsed.type === "delegated";
const activation = isDelegated
? await connection.getStakeActivation(pubkey)
: undefined;
data = {
program: result.data.program,
parsed,
activation,
};
break;
}
case "vote":
data = {
program: result.data.program,
2021-03-12 21:31:52 -08:00
parsed: create(info, VoteAccount),
};
break;
case "nonce":
data = {
program: result.data.program,
2021-03-12 21:31:52 -08:00
parsed: create(info, NonceAccount),
};
break;
case "sysvar":
data = {
program: result.data.program,
2021-03-12 21:31:52 -08:00
parsed: create(info, SysvarAccount),
};
break;
case "config":
data = {
program: result.data.program,
2021-03-12 21:31:52 -08:00
parsed: create(info, ConfigAccount),
};
break;
case "spl-token":
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
const parsed = create(info, TokenAccount);
let nftData;
try {
// Generate a PDA and check for a Metadata Account
if (parsed.type === "mint") {
const metadata = await Metadata.load(
connection,
await Metadata.getPDA(pubkey)
);
if (metadata) {
// We have a valid Metadata account. Try and pull edition data.
const editionInfo = await getEditionInfo(
metadata,
connection
);
const id = pubkeyToString(pubkey);
const metadataJSON = await getMetaDataJSON(
id,
metadata.data
);
nftData = {
metadata: metadata.data,
json: metadataJSON,
editionInfo,
};
}
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
}
} catch (error) {
// unable to find NFT metadata account
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
}
data = {
program: result.data.program,
Adding NFT support to the Explorer (#20009) * Adding NFT support to the explorer / copying over required Metaplex logic * Fixing a whitespace issue causing validation to fail * Removed MetadataProvider and instead metadata is being stamped on TokenProgramData * Fixing EOF new line sanity check issue * Added styling improvements to the Creator dropdown and NFT asset * Forgot to run Prettier * Creator address links were only redirecting to Mainnet. This redirects to the appropriate cluster * Removed dependencies not required for Explorer based use. Fixed package-lock.json because of a legacy npm version * Removed react-content-loader and popperjs * Removed MeshArt. Nobody likes VR anyways * Capped HTML animation asset width to 150px * Added an Editon check to properly identify NFTs * Refactoring away for un-necessary helpers * Dropped antd and added an image loading placeholder * Added a HTML animation flickering fix * Removed arweave check for valid uri properties * Resolving some nit comments and cleaning up * Adding Tooltips to better explain the content in the NFT Header * Started consuming MasterEdition data which is being used to display Seller Fee and Max Supply information in the Token Account Section * Fixing a bug where Edition NFTs weren't properly supported * Added better Edition support and labeling when there isn't Master Edition information added to metaplex metadata * Fixed Max Supply issue where 0 should be displayed as 1 * Updated tooltips to be shorter and more user friendly * Separting NFTHeader from AccountDetailsPage, adding a new TokenSection for NFTs and adding some cleanup
2021-10-05 11:30:05 -07:00
parsed,
nftData,
};
break;
default:
data = undefined;
}
} catch (error) {
reportError(error, { url, address: pubkey.toBase58() });
}
2020-05-14 00:30:33 -07:00
}
2020-04-06 03:54:29 -07:00
details = {
space,
2020-04-06 03:54:29 -07:00
executable: result.executable,
2020-05-14 00:30:33 -07:00
owner: result.owner,
2020-06-24 01:07:47 -07:00
data,
2020-04-06 03:54:29 -07:00
};
2020-04-05 01:31:40 -07:00
}
data = { pubkey, lamports, details };
2020-05-14 07:14:28 -07:00
fetchStatus = FetchStatus.Fetched;
2020-04-06 03:54:29 -07:00
} catch (error) {
if (cluster !== Cluster.Custom) {
reportError(error, { url });
}
2020-05-14 07:14:28 -07:00
fetchStatus = FetchStatus.FetchFailed;
2020-03-31 06:58:48 -07:00
}
dispatch({
type: ActionType.Update,
status: fetchStatus,
data,
key: pubkey.toBase58(),
url,
});
2020-04-21 08:30:52 -07:00
}
const getMetaDataJSON = async (
id: string,
metadata: programs.metadata.MetadataData
): Promise<MetadataJson | undefined> => {
return new Promise(async (resolve, reject) => {
const uri = metadata.data.uri;
if (!uri) return resolve(undefined);
const processJson = (extended: any) => {
if (!extended || extended?.properties?.files?.length === 0) {
return;
}
if (extended?.image) {
extended.image = extended.image.startsWith("http")
? extended.image
: `${metadata.data.uri}/${extended.image}`;
}
return extended;
};
try {
fetch(uri)
.then(async (_) => {
try {
const data = await _.json();
try {
localStorage.setItem(uri, JSON.stringify(data));
} catch {
// ignore
}
resolve(processJson(data));
} catch {
resolve(undefined);
}
})
.catch(() => {
resolve(undefined);
});
} catch (ex) {
console.error(ex);
resolve(undefined);
}
});
};
2020-03-31 06:58:48 -07:00
export function useAccounts() {
const context = React.useContext(StateContext);
if (!context) {
throw new Error(`useAccounts must be used within a AccountsProvider`);
}
return context.entries;
2020-03-31 06:58:48 -07:00
}
export function useAccountInfo(
address: string | undefined
): Cache.CacheEntry<Account> | undefined {
2020-04-21 08:30:52 -07:00
const context = React.useContext(StateContext);
2020-05-12 03:32:14 -07:00
2020-04-21 08:30:52 -07:00
if (!context) {
2020-05-12 03:32:14 -07:00
throw new Error(`useAccountInfo must be used within a AccountsProvider`);
2020-04-21 08:30:52 -07:00
}
if (address === undefined) return;
return context.entries[address];
2020-04-21 08:30:52 -07:00
}
export function useMintAccountInfo(
address: string | undefined
): MintAccountInfo | undefined {
const accountInfo = useAccountInfo(address);
return React.useMemo(() => {
if (address === undefined) return;
try {
const data = accountInfo?.data?.details?.data;
if (!data) return;
if (data.program !== "spl-token" || data.parsed.type !== "mint") {
return;
}
2021-03-12 21:31:52 -08:00
return create(data.parsed.info, MintAccountInfo);
} catch (err) {
reportError(err, { address });
}
}, [address, accountInfo]);
}
export function useTokenAccountInfo(
address: string | undefined
): TokenAccountInfo | undefined {
const accountInfo = useAccountInfo(address);
if (address === undefined) return;
try {
const data = accountInfo?.data?.details?.data;
if (!data) return;
if (data.program !== "spl-token" || data.parsed.type !== "account") {
return;
}
2021-03-12 21:31:52 -08:00
return create(data.parsed.info, TokenAccountInfo);
} catch (err) {
reportError(err, { address });
}
}
2020-05-12 03:32:14 -07:00
export function useFetchAccountInfo() {
const dispatch = React.useContext(DispatchContext);
if (!dispatch) {
throw new Error(
`useFetchAccountInfo must be used within a AccountsProvider`
);
}
const { cluster, url } = useCluster();
return React.useCallback(
(pubkey: PublicKey) => {
fetchAccountInfo(dispatch, pubkey, cluster, url);
},
[dispatch, cluster, url]
);
2020-05-12 03:32:14 -07:00
}