2020-03-31 06:58:48 -07:00
|
|
|
import React from "react";
|
2020-08-14 02:54:21 -07:00
|
|
|
import * as Sentry from "@sentry/react";
|
2020-08-08 06:06:24 -07:00
|
|
|
import { StakeAccount as StakeAccountWasm } from "solana-sdk-wasm";
|
2020-05-14 07:14:28 -07:00
|
|
|
import { PublicKey, Connection, StakeProgram } from "@solana/web3.js";
|
2020-08-07 09:38:20 -07:00
|
|
|
import { useCluster } from "../cluster";
|
2020-05-14 07:14:28 -07:00
|
|
|
import { HistoryProvider } from "./history";
|
2020-08-08 08:02:01 -07:00
|
|
|
import { TokensProvider, TOKEN_PROGRAM_ID } from "./tokens";
|
2020-08-08 06:06:24 -07:00
|
|
|
import { coerce } from "superstruct";
|
|
|
|
import { ParsedInfo } from "validators";
|
2020-08-08 08:02:01 -07:00
|
|
|
import { StakeAccount } from "validators/accounts/stake";
|
2020-08-29 05:50:45 -07:00
|
|
|
import {
|
|
|
|
TokenAccount,
|
|
|
|
MintAccountInfo,
|
|
|
|
TokenAccountInfo,
|
|
|
|
} from "validators/accounts/token";
|
2020-08-12 07:41:04 -07:00
|
|
|
import * as Cache from "providers/cache";
|
|
|
|
import { ActionType, FetchStatus } from "providers/cache";
|
2020-05-14 07:14:28 -07:00
|
|
|
export { useAccountHistory } from "./history";
|
2020-03-31 06:58:48 -07:00
|
|
|
|
2020-08-08 08:02:01 -07:00
|
|
|
export type StakeProgramData = {
|
2020-08-29 05:50:45 -07:00
|
|
|
program: "stake";
|
2020-08-08 08:02:01 -07:00
|
|
|
parsed: StakeAccount | StakeAccountWasm;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type TokenProgramData = {
|
2020-08-29 05:50:45 -07:00
|
|
|
program: "spl-token";
|
2020-08-08 08:02:01 -07:00
|
|
|
parsed: TokenAccount;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type ProgramData = StakeProgramData | TokenProgramData;
|
|
|
|
|
2020-03-31 06:58:48 -07:00
|
|
|
export interface Details {
|
|
|
|
executable: boolean;
|
|
|
|
owner: PublicKey;
|
2020-08-08 06:06:24 -07:00
|
|
|
space?: number;
|
2020-08-08 08:02:01 -07:00
|
|
|
data?: ProgramData;
|
2020-03-31 06:58:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Account {
|
|
|
|
pubkey: PublicKey;
|
2020-08-12 07:41:04 -07:00
|
|
|
lamports: number;
|
2020-03-31 06:58:48 -07:00
|
|
|
details?: Details;
|
|
|
|
}
|
|
|
|
|
2020-08-12 07:41:04 -07:00
|
|
|
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) {
|
2020-08-08 05:47:07 -07:00
|
|
|
const { url } = useCluster();
|
2020-08-12 07:41:04 -07:00
|
|
|
const [state, dispatch] = Cache.useReducer<Account>(url);
|
2020-03-31 06:58:48 -07:00
|
|
|
|
2020-08-12 07:41:04 -07:00
|
|
|
// Clear accounts cache whenever cluster is changed
|
2020-03-31 06:58:48 -07:00
|
|
|
React.useEffect(() => {
|
2020-08-08 05:47:07 -07:00
|
|
|
dispatch({ type: ActionType.Clear, url });
|
2020-08-12 07:41:04 -07:00
|
|
|
}, [dispatch, url]);
|
2020-03-31 06:58:48 -07:00
|
|
|
|
|
|
|
return (
|
|
|
|
<StateContext.Provider value={state}>
|
|
|
|
<DispatchContext.Provider value={dispatch}>
|
2020-08-02 06:55:36 -07:00
|
|
|
<TokensProvider>
|
|
|
|
<HistoryProvider>{children}</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,
|
2020-08-02 05:18:28 -07:00
|
|
|
url: string
|
2020-03-31 06:58:48 -07:00
|
|
|
) {
|
|
|
|
dispatch({
|
2020-08-12 07:41:04 -07:00
|
|
|
type: ActionType.Update,
|
|
|
|
key: pubkey.toBase58(),
|
|
|
|
status: Cache.FetchStatus.Fetching,
|
2020-08-08 05:47:07 -07:00
|
|
|
url,
|
2020-03-31 06:58:48 -07:00
|
|
|
});
|
|
|
|
|
2020-08-12 07:41:04 -07:00
|
|
|
let data;
|
2020-05-12 03:32:14 -07:00
|
|
|
let fetchStatus;
|
2020-03-31 06:58:48 -07:00
|
|
|
try {
|
2020-08-08 06:06:24 -07:00
|
|
|
const result = (
|
|
|
|
await new Connection(url, "single").getParsedAccountInfo(pubkey)
|
|
|
|
).value;
|
2020-08-12 07:41:04 -07:00
|
|
|
|
|
|
|
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
|
2020-08-08 06:06:24 -07:00
|
|
|
let space;
|
|
|
|
if (!("parsed" in result.data)) {
|
|
|
|
space = result.data.length;
|
|
|
|
}
|
|
|
|
|
2020-08-08 08:02:01 -07:00
|
|
|
let data: ProgramData | undefined;
|
2020-05-14 00:30:33 -07:00
|
|
|
if (result.owner.equals(StakeProgram.programId)) {
|
2020-05-14 08:20:35 -07:00
|
|
|
try {
|
2020-08-08 08:02:01 -07:00
|
|
|
let parsed;
|
2020-08-08 06:06:24 -07:00
|
|
|
if ("parsed" in result.data) {
|
|
|
|
const info = coerce(result.data.parsed, ParsedInfo);
|
2020-08-08 08:02:01 -07:00
|
|
|
parsed = coerce(info, StakeAccount);
|
2020-08-08 06:06:24 -07:00
|
|
|
} else {
|
|
|
|
const wasm = await import("solana-sdk-wasm");
|
2020-08-08 08:02:01 -07:00
|
|
|
parsed = wasm.StakeAccount.fromAccountData(result.data);
|
2020-08-08 06:06:24 -07:00
|
|
|
}
|
2020-08-08 08:02:01 -07:00
|
|
|
data = {
|
2020-08-29 05:50:45 -07:00
|
|
|
program: "stake",
|
2020-08-08 08:02:01 -07:00
|
|
|
parsed,
|
|
|
|
};
|
2020-05-14 08:20:35 -07:00
|
|
|
} catch (err) {
|
2020-08-14 02:54:21 -07:00
|
|
|
Sentry.captureException(err, {
|
|
|
|
tags: { url, address: pubkey.toBase58() },
|
|
|
|
});
|
2020-05-14 08:20:35 -07:00
|
|
|
// TODO store error state in Account info
|
|
|
|
}
|
2020-08-08 08:02:01 -07:00
|
|
|
} 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 = {
|
2020-08-29 05:50:45 -07:00
|
|
|
program: "spl-token",
|
2020-08-08 08:02:01 -07:00
|
|
|
parsed,
|
|
|
|
};
|
|
|
|
} catch (err) {
|
2020-08-14 02:54:21 -07:00
|
|
|
Sentry.captureException(err, {
|
|
|
|
tags: { url, address: pubkey.toBase58() },
|
|
|
|
});
|
2020-08-08 08:02:01 -07:00
|
|
|
// TODO store error state in Account info
|
|
|
|
}
|
|
|
|
}
|
2020-05-14 00:30:33 -07:00
|
|
|
}
|
|
|
|
|
2020-04-06 03:54:29 -07:00
|
|
|
details = {
|
2020-08-08 06:06:24 -07:00
|
|
|
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
|
|
|
}
|
2020-08-12 07:41:04 -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) {
|
2020-08-14 02:54:21 -07:00
|
|
|
Sentry.captureException(error, { tags: { url } });
|
2020-05-14 07:14:28 -07:00
|
|
|
fetchStatus = FetchStatus.FetchFailed;
|
2020-03-31 06:58:48 -07:00
|
|
|
}
|
2020-08-12 07:41:04 -07:00
|
|
|
dispatch({
|
|
|
|
type: ActionType.Update,
|
|
|
|
status: fetchStatus,
|
|
|
|
data,
|
|
|
|
key: pubkey.toBase58(),
|
|
|
|
url,
|
|
|
|
});
|
2020-04-21 08:30:52 -07:00
|
|
|
}
|
|
|
|
|
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`);
|
|
|
|
}
|
2020-08-12 07:41:04 -07:00
|
|
|
return context.entries;
|
2020-03-31 06:58:48 -07:00
|
|
|
}
|
|
|
|
|
2020-08-12 07:41:04 -07:00
|
|
|
export function useAccountInfo(
|
2020-08-29 05:50:45 -07:00
|
|
|
address: string | undefined
|
2020-08-12 07:41:04 -07:00
|
|
|
): 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
|
|
|
}
|
2020-08-29 05:50:45 -07:00
|
|
|
if (address === undefined) return;
|
2020-08-12 07:41:04 -07:00
|
|
|
return context.entries[address];
|
2020-04-21 08:30:52 -07:00
|
|
|
}
|
|
|
|
|
2020-08-29 05:50:45 -07:00
|
|
|
export function useMintAccountInfo(
|
|
|
|
address: string | undefined
|
|
|
|
): MintAccountInfo | 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 !== "mint") {
|
|
|
|
throw new Error("Expected mint");
|
|
|
|
}
|
|
|
|
|
|
|
|
return coerce(data.parsed.info, MintAccountInfo);
|
|
|
|
} catch (err) {
|
|
|
|
Sentry.captureException(err, {
|
|
|
|
tags: { address },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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") {
|
|
|
|
throw new Error("Expected token account");
|
|
|
|
}
|
|
|
|
|
|
|
|
return coerce(data.parsed.info, TokenAccountInfo);
|
|
|
|
} catch (err) {
|
|
|
|
Sentry.captureException(err, {
|
|
|
|
tags: { 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`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-02 05:18:28 -07:00
|
|
|
const { url } = useCluster();
|
2020-08-29 05:50:45 -07:00
|
|
|
return React.useCallback(
|
|
|
|
(pubkey: PublicKey) => {
|
|
|
|
fetchAccountInfo(dispatch, pubkey, url);
|
|
|
|
},
|
|
|
|
[dispatch, url]
|
|
|
|
);
|
2020-05-12 03:32:14 -07:00
|
|
|
}
|