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";
|
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-02 06:55:36 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-04-01 01:35:12 -07:00
|
|
|
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,
|
2020-08-02 05:18:28 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-08-02 05:18:28 -07:00
|
|
|
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: {
|
2020-04-01 01:35:12 -07:00
|
|
|
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
|
|
|
};
|
2020-08-07 09:38:20 -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
|
|
|
};
|
2020-08-07 09:38:20 -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;
|
|
|
|
}
|
2020-08-02 05:18:28 -07:00
|
|
|
|
|
|
|
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
|
|
|
|
2020-08-07 09:38:20 -07:00
|
|
|
// Clear account statuses whenever cluster is changed
|
|
|
|
const { url } = useCluster();
|
2020-03-31 06:58:48 -07:00
|
|
|
React.useEffect(() => {
|
2020-08-07 09:38:20 -07:00
|
|
|
dispatch({ type: ActionType.Clear });
|
|
|
|
}, [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-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`);
|
|
|
|
}
|
2020-08-02 05:18:28 -07:00
|
|
|
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`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-02 05:18:28 -07:00
|
|
|
const { url } = useCluster();
|
2020-05-12 03:32:14 -07:00
|
|
|
return (pubkey: PublicKey) => {
|
2020-08-02 05:18:28 -07:00
|
|
|
fetchAccountInfo(dispatch, pubkey, url);
|
2020-05-12 03:32:14 -07:00
|
|
|
};
|
|
|
|
}
|