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

219 lines
5.0 KiB
TypeScript
Raw Normal View History

2020-03-31 06:58:48 -07:00
import React from "react";
2020-05-14 08:20:35 -07:00
import { StakeAccount } from "solana-sdk-wasm";
2020-05-14 07:14:28 -07:00
import { PublicKey, Connection, StakeProgram } from "@solana/web3.js";
import { useCluster } from "../cluster";
2020-05-14 07:14:28 -07:00
import { HistoryProvider } from "./history";
import { TokensProvider } from "./tokens";
2020-05-14 07:14:28 -07:00
export { useAccountHistory } from "./history";
2020-03-31 06:58:48 -07:00
2020-05-14 07:14:28 -07:00
export enum FetchStatus {
Fetching,
FetchFailed,
2020-06-24 01:07:47 -07:00
Fetched,
2020-03-31 06:58:48 -07:00
}
export interface Details {
executable: boolean;
owner: PublicKey;
space: number;
2020-05-14 08:20:35 -07:00
data?: StakeAccount;
2020-03-31 06:58:48 -07:00
}
export interface Account {
pubkey: PublicKey;
2020-05-14 07:14:28 -07:00
status: FetchStatus;
2020-04-05 01:31:40 -07:00
lamports?: number;
2020-03-31 06:58:48 -07:00
details?: Details;
}
type Accounts = { [address: string]: Account };
2020-03-31 06:58:48 -07:00
interface State {
accounts: Accounts;
}
export enum ActionType {
Update,
2020-06-24 01:07:47 -07:00
Fetch,
Clear,
2020-03-31 06:58:48 -07:00
}
interface Update {
type: ActionType.Update;
2020-05-12 03:32:14 -07:00
pubkey: PublicKey;
2020-04-21 08:30:52 -07:00
data: {
2020-05-14 07:14:28 -07:00
status: FetchStatus;
2020-04-21 08:30:52 -07:00
lamports?: number;
details?: Details;
};
2020-03-31 06:58:48 -07:00
}
2020-05-12 03:32:14 -07:00
interface Fetch {
type: ActionType.Fetch;
2020-03-31 06:58:48 -07:00
pubkey: PublicKey;
}
interface Clear {
type: ActionType.Clear;
}
type Action = Update | Fetch | Clear;
2020-05-14 07:14:28 -07:00
type Dispatch = (action: Action) => void;
2020-03-31 06:58:48 -07:00
function reducer(state: State, action: Action): State {
switch (action.type) {
2020-05-12 03:32:14 -07:00
case ActionType.Fetch: {
const address = action.pubkey.toBase58();
2020-05-12 03:32:14 -07:00
const account = state.accounts[address];
if (account) {
const accounts = {
...state.accounts,
[address]: {
pubkey: account.pubkey,
2020-06-24 01:07:47 -07:00
status: FetchStatus.Fetching,
},
2020-05-12 03:32:14 -07:00
};
return { ...state, accounts };
2020-05-12 03:32:14 -07:00
} else {
const accounts = {
...state.accounts,
[address]: {
2020-05-14 07:14:28 -07:00
status: FetchStatus.Fetching,
2020-06-24 01:07:47 -07:00
pubkey: action.pubkey,
},
2020-05-12 03:32:14 -07:00
};
return { ...state, accounts };
2020-05-12 03:32:14 -07:00
}
2020-04-21 08:30:52 -07:00
}
2020-03-31 06:58:48 -07:00
case ActionType.Update: {
2020-05-12 03:32:14 -07:00
const address = action.pubkey.toBase58();
const account = state.accounts[address];
2020-03-31 06:58:48 -07:00
if (account) {
const accounts = {
...state.accounts,
2020-05-12 03:32:14 -07:00
[address]: {
...account,
2020-06-24 01:07:47 -07:00
...action.data,
},
2020-03-31 06:58:48 -07:00
};
return { ...state, accounts };
}
break;
}
case ActionType.Clear: {
return {
...state,
accounts: {},
};
}
2020-03-31 06:58:48 -07:00
}
return state;
}
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-05-12 03:32:14 -07:00
const [state, dispatch] = React.useReducer(reducer, {
2020-06-24 01:07:47 -07:00
accounts: {},
2020-05-12 03:32:14 -07:00
});
2020-03-31 06:58:48 -07:00
// Clear account statuses whenever cluster is changed
const { url } = useCluster();
2020-03-31 06:58:48 -07:00
React.useEffect(() => {
dispatch({ type: ActionType.Clear });
}, [url]);
2020-03-31 06:58:48 -07:00
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<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,
url: string
2020-03-31 06:58:48 -07:00
) {
dispatch({
2020-05-12 03:32:14 -07:00
type: ActionType.Fetch,
2020-06-24 01:07:47 -07:00
pubkey,
2020-03-31 06:58:48 -07:00
});
2020-05-12 03:32:14 -07:00
let fetchStatus;
2020-03-31 06:58:48 -07:00
let details;
2020-04-05 01:31:40 -07:00
let lamports;
2020-03-31 06:58:48 -07:00
try {
2020-05-12 03:32:14 -07:00
const result = await new Connection(url, "recent").getAccountInfo(pubkey);
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
let data = undefined;
// Only save data in memory if we can decode it
if (result.owner.equals(StakeProgram.programId)) {
2020-05-14 08:20:35 -07:00
try {
const wasm = await import("solana-sdk-wasm");
data = wasm.StakeAccount.fromAccountData(result.data);
} catch (err) {
console.error("Unexpected error loading wasm", err);
// TODO store error state in Account info
}
2020-05-14 00:30:33 -07:00
}
2020-04-06 03:54:29 -07:00
details = {
space: result.data.length,
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-05-14 07:14:28 -07:00
fetchStatus = FetchStatus.Fetched;
2020-04-06 03:54:29 -07:00
} catch (error) {
console.error("Failed to fetch account info", error);
2020-05-14 07:14:28 -07:00
fetchStatus = FetchStatus.FetchFailed;
2020-03-31 06:58:48 -07:00
}
2020-05-12 03:32:14 -07:00
const data = { status: fetchStatus, lamports, details };
dispatch({ type: ActionType.Update, data, pubkey });
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`);
}
return context;
2020-03-31 06:58:48 -07:00
}
2020-05-12 03:32:14 -07:00
export function useAccountInfo(address: string) {
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-05-12 03:32:14 -07:00
return context.accounts[address];
2020-04-21 08:30:52 -07:00
}
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 { url } = useCluster();
2020-05-12 03:32:14 -07:00
return (pubkey: PublicKey) => {
fetchAccountInfo(dispatch, pubkey, url);
2020-05-12 03:32:14 -07:00
};
}