Use common provider for explorer cached data (#11582)
This commit is contained in:
parent
8ddb116659
commit
a992bb5f94
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { FetchStatus } from "providers/accounts";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
import {
|
||||
useFetchAccountOwnedTokens,
|
||||
useAccountOwnedTokens,
|
||||
|
@ -24,7 +24,8 @@ export function OwnedTokensCard({ pubkey }: { pubkey: PublicKey }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { status, tokens } = ownedTokens;
|
||||
const { status } = ownedTokens;
|
||||
const tokens = ownedTokens.data?.tokens;
|
||||
const fetching = status === FetchStatus.Fetching;
|
||||
if (fetching && (tokens === undefined || tokens.length === 0)) {
|
||||
return <LoadingCard message="Loading owned tokens" />;
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
ConfirmedSignatureInfo,
|
||||
ParsedInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { FetchStatus } from "providers/accounts";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
import {
|
||||
useAccountHistories,
|
||||
useFetchAccountHistory,
|
||||
|
@ -34,7 +34,7 @@ export function TokenHistoryCard({ pubkey }: { pubkey: PublicKey }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { tokens } = ownedTokens;
|
||||
const tokens = ownedTokens.data?.tokens;
|
||||
if (tokens === undefined || tokens.length === 0) return null;
|
||||
|
||||
return <TokenHistoryTable tokens={tokens} />;
|
||||
|
@ -62,17 +62,17 @@ function TokenHistoryTable({ tokens }: { tokens: TokenInfoWithPubkey[] }) {
|
|||
|
||||
const fetchedFullHistory = tokens.every((token) => {
|
||||
const history = accountHistories[token.pubkey.toBase58()];
|
||||
return history && history.foundOldest === true;
|
||||
return history?.data?.foundOldest === true;
|
||||
});
|
||||
|
||||
const fetching = tokens.some((token) => {
|
||||
const history = accountHistories[token.pubkey.toBase58()];
|
||||
return history && history.status === FetchStatus.Fetching;
|
||||
return history?.status === FetchStatus.Fetching;
|
||||
});
|
||||
|
||||
const failed = tokens.some((token) => {
|
||||
const history = accountHistories[token.pubkey.toBase58()];
|
||||
return history && history.status === FetchStatus.FetchFailed;
|
||||
return history?.status === FetchStatus.FetchFailed;
|
||||
});
|
||||
|
||||
const mintAndTxs = tokens
|
||||
|
@ -81,12 +81,13 @@ function TokenHistoryTable({ tokens }: { tokens: TokenInfoWithPubkey[] }) {
|
|||
history: accountHistories[token.pubkey.toBase58()],
|
||||
}))
|
||||
.filter(({ history }) => {
|
||||
return (
|
||||
history !== undefined && history.fetched && history.fetched.length > 0
|
||||
);
|
||||
return history?.data?.fetched && history.data.fetched.length > 0;
|
||||
})
|
||||
.flatMap(({ mint, history }) =>
|
||||
(history.fetched as ConfirmedSignatureInfo[]).map((tx) => ({ mint, tx }))
|
||||
(history?.data?.fetched as ConfirmedSignatureInfo[]).map((tx) => ({
|
||||
mint,
|
||||
tx,
|
||||
}))
|
||||
);
|
||||
|
||||
if (mintAndTxs.length === 0) {
|
||||
|
@ -196,7 +197,8 @@ function TokenTransactionRow({
|
|||
if (!details) fetchDetails(tx.signature);
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const instructions = details?.transaction?.transaction.message.instructions;
|
||||
const instructions =
|
||||
details?.data?.transaction?.transaction.message.instructions;
|
||||
if (instructions) {
|
||||
const tokenInstructions = instructions.filter(
|
||||
(ix) => "parsed" in ix && ix.program === "spl-token"
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import React from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
FetchStatus,
|
||||
useAccountInfo,
|
||||
useAccountHistory,
|
||||
} from "providers/accounts";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
import { useAccountInfo, useAccountHistory } from "providers/accounts";
|
||||
import { useFetchAccountHistory } from "providers/accounts/history";
|
||||
import { Signature } from "components/common/Signature";
|
||||
import { ErrorCard } from "components/common/ErrorCard";
|
||||
|
@ -22,9 +19,11 @@ export function TransactionHistoryCard({ pubkey }: { pubkey: PublicKey }) {
|
|||
if (!history) refresh();
|
||||
}, [address]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (!info || !history || info.lamports === undefined) {
|
||||
if (!history || info?.data === undefined) {
|
||||
return null;
|
||||
} else if (history.fetched === undefined) {
|
||||
}
|
||||
|
||||
if (history?.data === undefined) {
|
||||
if (history.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard message="Loading history" />;
|
||||
}
|
||||
|
@ -34,7 +33,8 @@ export function TransactionHistoryCard({ pubkey }: { pubkey: PublicKey }) {
|
|||
);
|
||||
}
|
||||
|
||||
if (history.fetched.length === 0) {
|
||||
const transactions = history.data.fetched;
|
||||
if (transactions.length === 0) {
|
||||
if (history.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard message="Loading history" />;
|
||||
}
|
||||
|
@ -48,8 +48,6 @@ export function TransactionHistoryCard({ pubkey }: { pubkey: PublicKey }) {
|
|||
}
|
||||
|
||||
const detailsList: React.ReactNode[] = [];
|
||||
const transactions = history.fetched;
|
||||
|
||||
for (var i = 0; i < transactions.length; i++) {
|
||||
const slot = transactions[i].slot;
|
||||
const slotTransactions = [transactions[i]];
|
||||
|
@ -126,7 +124,7 @@ export function TransactionHistoryCard({ pubkey }: { pubkey: PublicKey }) {
|
|||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
{history.foundOldest ? (
|
||||
{history.data.foundOldest ? (
|
||||
<div className="text-muted text-center">Fetched full history</div>
|
||||
) : (
|
||||
<button
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import React from "react";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
FetchStatus,
|
||||
useFetchAccountInfo,
|
||||
useAccountInfo,
|
||||
} from "providers/accounts";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
import { useFetchAccountInfo, useAccountInfo } from "providers/accounts";
|
||||
import { StakeAccountSection } from "components/account/StakeAccountSection";
|
||||
import { TokenAccountSection } from "components/account/TokenAccountSection";
|
||||
import { ErrorCard } from "components/common/ErrorCard";
|
||||
|
@ -62,12 +59,13 @@ function InfoSection({ pubkey }: { pubkey: PublicKey }) {
|
|||
return <LoadingCard />;
|
||||
} else if (
|
||||
info.status === FetchStatus.FetchFailed ||
|
||||
info.lamports === undefined
|
||||
info.data?.lamports === undefined
|
||||
) {
|
||||
return <ErrorCard retry={() => refresh(pubkey)} text="Fetch Failed" />;
|
||||
}
|
||||
|
||||
const data = info.details?.data;
|
||||
const account = info.data;
|
||||
const data = account?.details?.data;
|
||||
if (data && data.name === "stake") {
|
||||
let stakeAccountType, stakeAccount;
|
||||
if ("accountType" in data.parsed) {
|
||||
|
@ -80,15 +78,15 @@ function InfoSection({ pubkey }: { pubkey: PublicKey }) {
|
|||
|
||||
return (
|
||||
<StakeAccountSection
|
||||
account={info}
|
||||
account={account}
|
||||
stakeAccount={stakeAccount}
|
||||
stakeAccountType={stakeAccountType}
|
||||
/>
|
||||
);
|
||||
} else if (data && data.name === "spl-token") {
|
||||
return <TokenAccountSection account={info} tokenAccount={data.parsed} />;
|
||||
return <TokenAccountSection account={account} tokenAccount={data.parsed} />;
|
||||
} else {
|
||||
return <UnknownAccountCard account={info} />;
|
||||
return <UnknownAccountCard account={account} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +94,7 @@ type MoreTabs = "history" | "tokens";
|
|||
function MoreSection({ pubkey, tab }: { pubkey: PublicKey; tab: MoreTabs }) {
|
||||
const address = pubkey.toBase58();
|
||||
const info = useAccountInfo(address);
|
||||
if (!info || info.lamports === undefined) return null;
|
||||
if (info?.data === undefined) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
useFetchTransactionStatus,
|
||||
useTransactionStatus,
|
||||
useTransactionDetails,
|
||||
FetchStatus,
|
||||
} from "providers/transactions";
|
||||
import { useFetchTransactionDetails } from "providers/transactions/details";
|
||||
import { useCluster, ClusterStatus } from "providers/cluster";
|
||||
|
@ -27,6 +26,7 @@ import { Address } from "components/common/Address";
|
|||
import { Signature } from "components/common/Signature";
|
||||
import { intoTransactionInstruction } from "utils/tx";
|
||||
import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard";
|
||||
import { FetchStatus } from "providers/cache";
|
||||
|
||||
type Props = { signature: TransactionSignature };
|
||||
export function TransactionDetailsPage({ signature }: Props) {
|
||||
|
@ -67,13 +67,13 @@ function StatusCard({ signature }: Props) {
|
|||
}
|
||||
}, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (!status || status.fetchStatus === FetchStatus.Fetching) {
|
||||
if (!status || status.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard />;
|
||||
} else if (status?.fetchStatus === FetchStatus.FetchFailed) {
|
||||
} else if (status.status === FetchStatus.FetchFailed) {
|
||||
return (
|
||||
<ErrorCard retry={() => fetchStatus(signature)} text="Fetch Failed" />
|
||||
);
|
||||
} else if (!status.info) {
|
||||
} else if (!status.data?.info) {
|
||||
if (firstAvailableBlock !== undefined) {
|
||||
return (
|
||||
<ErrorCard
|
||||
|
@ -86,7 +86,7 @@ function StatusCard({ signature }: Props) {
|
|||
return <ErrorCard retry={() => fetchStatus(signature)} text="Not Found" />;
|
||||
}
|
||||
|
||||
const { info } = status;
|
||||
const { info } = status.data;
|
||||
const renderResult = () => {
|
||||
let statusClass = "success";
|
||||
let statusText = "Success";
|
||||
|
@ -102,8 +102,8 @@ function StatusCard({ signature }: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const fee = details?.transaction?.meta?.fee;
|
||||
const transaction = details?.transaction?.transaction;
|
||||
const fee = details?.data?.transaction?.meta?.fee;
|
||||
const transaction = details?.data?.transaction?.transaction;
|
||||
const blockhash = transaction?.message.recentBlockhash;
|
||||
const isNonce = (() => {
|
||||
if (!transaction) return false;
|
||||
|
@ -203,18 +203,18 @@ function AccountsCard({ signature }: Props) {
|
|||
const fetchDetails = useFetchTransactionDetails();
|
||||
const refreshStatus = () => fetchStatus(signature);
|
||||
const refreshDetails = () => fetchDetails(signature);
|
||||
const transaction = details?.transaction?.transaction;
|
||||
const transaction = details?.data?.transaction?.transaction;
|
||||
const message = transaction?.message;
|
||||
const status = useTransactionStatus(signature);
|
||||
|
||||
// Fetch details on load
|
||||
React.useEffect(() => {
|
||||
if (status?.info?.confirmations === "max" && !details) {
|
||||
if (status?.data?.info?.confirmations === "max" && !details) {
|
||||
fetchDetails(signature);
|
||||
}
|
||||
}, [signature, details, status, fetchDetails]);
|
||||
|
||||
if (!status || !status.info) {
|
||||
if (!status?.data?.info) {
|
||||
return null;
|
||||
} else if (!details) {
|
||||
return (
|
||||
|
@ -223,15 +223,15 @@ function AccountsCard({ signature }: Props) {
|
|||
text="Details are not available until the transaction reaches MAX confirmations"
|
||||
/>
|
||||
);
|
||||
} else if (details.fetchStatus === FetchStatus.Fetching) {
|
||||
} else if (details.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard />;
|
||||
} else if (details?.fetchStatus === FetchStatus.FetchFailed) {
|
||||
} else if (details.status === FetchStatus.FetchFailed) {
|
||||
return <ErrorCard retry={refreshDetails} text="Fetch Failed" />;
|
||||
} else if (!details.transaction || !message) {
|
||||
} else if (!details.data?.transaction || !message) {
|
||||
return <ErrorCard retry={refreshDetails} text="Not Found" />;
|
||||
}
|
||||
|
||||
const { meta } = details.transaction;
|
||||
const { meta } = details.data.transaction;
|
||||
if (!meta) {
|
||||
if (isCached(url, signature)) {
|
||||
return null;
|
||||
|
@ -308,14 +308,14 @@ function InstructionsSection({ signature }: Props) {
|
|||
const fetchDetails = useFetchTransactionDetails();
|
||||
const refreshDetails = () => fetchDetails(signature);
|
||||
|
||||
if (!status || !status.info || !details || !details.transaction) return null;
|
||||
if (!status?.data?.info || !details?.data?.transaction) return null;
|
||||
|
||||
const { transaction } = details.transaction;
|
||||
const { transaction } = details.data.transaction;
|
||||
if (transaction.message.instructions.length === 0) {
|
||||
return <ErrorCard retry={refreshDetails} text="No instructions found" />;
|
||||
}
|
||||
|
||||
const result = status.info.result;
|
||||
const result = status.data.info.result;
|
||||
const instructionDetails = transaction.message.instructions.map(
|
||||
(next, index) => {
|
||||
if ("parsed" in next) {
|
||||
|
|
|
@ -5,51 +5,29 @@ import {
|
|||
TransactionSignature,
|
||||
Connection,
|
||||
} from "@solana/web3.js";
|
||||
import { FetchStatus } from "./index";
|
||||
import { useCluster } from "../cluster";
|
||||
import * as Cache from "providers/cache";
|
||||
import { ActionType, FetchStatus } from "providers/cache";
|
||||
|
||||
interface AccountHistory {
|
||||
status: FetchStatus;
|
||||
fetched?: ConfirmedSignatureInfo[];
|
||||
type AccountHistory = {
|
||||
fetched: ConfirmedSignatureInfo[];
|
||||
foundOldest: boolean;
|
||||
}
|
||||
|
||||
type State = {
|
||||
url: string;
|
||||
map: { [address: string]: AccountHistory };
|
||||
};
|
||||
|
||||
export enum ActionType {
|
||||
Update,
|
||||
Clear,
|
||||
}
|
||||
|
||||
interface Update {
|
||||
type: ActionType.Update;
|
||||
url: string;
|
||||
pubkey: PublicKey;
|
||||
status: FetchStatus;
|
||||
fetched?: ConfirmedSignatureInfo[];
|
||||
type HistoryUpdate = {
|
||||
history?: AccountHistory;
|
||||
before?: TransactionSignature;
|
||||
foundOldest?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
interface Clear {
|
||||
type: ActionType.Clear;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Action = Update | Clear;
|
||||
type Dispatch = (action: Action) => void;
|
||||
type State = Cache.State<AccountHistory>;
|
||||
type Dispatch = Cache.Dispatch<HistoryUpdate>;
|
||||
|
||||
function combineFetched(
|
||||
fetched: ConfirmedSignatureInfo[] | undefined,
|
||||
fetched: ConfirmedSignatureInfo[],
|
||||
current: ConfirmedSignatureInfo[] | undefined,
|
||||
before: TransactionSignature | undefined
|
||||
) {
|
||||
if (fetched === undefined) {
|
||||
return current;
|
||||
} else if (current === undefined) {
|
||||
if (current === undefined) {
|
||||
return fetched;
|
||||
}
|
||||
|
||||
|
@ -60,46 +38,19 @@ function combineFetched(
|
|||
}
|
||||
}
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case ActionType.Update: {
|
||||
if (action.url !== state.url) return state;
|
||||
const address = action.pubkey.toBase58();
|
||||
if (state.map[address]) {
|
||||
return {
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
[address]: {
|
||||
status: action.status,
|
||||
fetched: combineFetched(
|
||||
action.fetched,
|
||||
state.map[address].fetched,
|
||||
action.before
|
||||
),
|
||||
foundOldest: action.foundOldest || state.map[address].foundOldest,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
[address]: {
|
||||
status: action.status,
|
||||
fetched: action.fetched,
|
||||
foundOldest: action.foundOldest || false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
case ActionType.Clear: {
|
||||
return { url: action.url, map: {} };
|
||||
}
|
||||
}
|
||||
function reconcile(
|
||||
history: AccountHistory | undefined,
|
||||
update: HistoryUpdate | undefined
|
||||
) {
|
||||
if (update?.history === undefined) return;
|
||||
return {
|
||||
fetched: combineFetched(
|
||||
update.history.fetched,
|
||||
history?.fetched,
|
||||
update?.before
|
||||
),
|
||||
foundOldest: update?.history?.foundOldest || history?.foundOldest || false,
|
||||
};
|
||||
}
|
||||
|
||||
const StateContext = React.createContext<State | undefined>(undefined);
|
||||
|
@ -108,11 +59,11 @@ const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
|||
type HistoryProviderProps = { children: React.ReactNode };
|
||||
export function HistoryProvider({ children }: HistoryProviderProps) {
|
||||
const { url } = useCluster();
|
||||
const [state, dispatch] = React.useReducer(reducer, { url, map: {} });
|
||||
const [state, dispatch] = Cache.useCustomReducer(url, reconcile);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch({ type: ActionType.Clear, url });
|
||||
}, [url]);
|
||||
}, [dispatch, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
|
@ -132,20 +83,22 @@ async function fetchAccountHistory(
|
|||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: FetchStatus.Fetching,
|
||||
pubkey,
|
||||
key: pubkey.toBase58(),
|
||||
url,
|
||||
});
|
||||
|
||||
let status;
|
||||
let fetched;
|
||||
let foundOldest;
|
||||
let history;
|
||||
try {
|
||||
const connection = new Connection(url);
|
||||
fetched = await connection.getConfirmedSignaturesForAddress2(
|
||||
const fetched = await connection.getConfirmedSignaturesForAddress2(
|
||||
pubkey,
|
||||
options
|
||||
);
|
||||
foundOldest = fetched.length < options.limit;
|
||||
history = {
|
||||
fetched,
|
||||
foundOldest: fetched.length < options.limit,
|
||||
};
|
||||
status = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch account history", error);
|
||||
|
@ -154,11 +107,12 @@ async function fetchAccountHistory(
|
|||
dispatch({
|
||||
type: ActionType.Update,
|
||||
url,
|
||||
key: pubkey.toBase58(),
|
||||
status,
|
||||
fetched,
|
||||
before: options?.before,
|
||||
pubkey,
|
||||
foundOldest,
|
||||
data: {
|
||||
history,
|
||||
before: options?.before,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -171,17 +125,19 @@ export function useAccountHistories() {
|
|||
);
|
||||
}
|
||||
|
||||
return context.map;
|
||||
return context.entries;
|
||||
}
|
||||
|
||||
export function useAccountHistory(address: string) {
|
||||
export function useAccountHistory(
|
||||
address: string
|
||||
): Cache.CacheEntry<AccountHistory> | undefined {
|
||||
const context = React.useContext(StateContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`useAccountHistory must be used within a AccountsProvider`);
|
||||
}
|
||||
|
||||
return context.map[address];
|
||||
return context.entries[address];
|
||||
}
|
||||
|
||||
export function useFetchAccountHistory() {
|
||||
|
@ -195,10 +151,11 @@ export function useFetchAccountHistory() {
|
|||
}
|
||||
|
||||
return (pubkey: PublicKey, refresh?: boolean) => {
|
||||
const before = state.map[pubkey.toBase58()];
|
||||
if (!refresh && before && before.fetched && before.fetched.length > 0) {
|
||||
if (before.foundOldest) return;
|
||||
const oldest = before.fetched[before.fetched.length - 1].signature;
|
||||
const before = state.entries[pubkey.toBase58()];
|
||||
if (!refresh && before?.data?.fetched && before.data.fetched.length > 0) {
|
||||
if (before.data.foundOldest) return;
|
||||
const oldest =
|
||||
before.data.fetched[before.data.fetched.length - 1].signature;
|
||||
fetchAccountHistory(dispatch, pubkey, url, { before: oldest, limit: 25 });
|
||||
} else {
|
||||
fetchAccountHistory(dispatch, pubkey, url, { limit: 25 });
|
||||
|
|
|
@ -8,14 +8,10 @@ import { coerce } from "superstruct";
|
|||
import { ParsedInfo } from "validators";
|
||||
import { StakeAccount } from "validators/accounts/stake";
|
||||
import { TokenAccount } from "validators/accounts/token";
|
||||
import * as Cache from "providers/cache";
|
||||
import { ActionType, FetchStatus } from "providers/cache";
|
||||
export { useAccountHistory } from "./history";
|
||||
|
||||
export enum FetchStatus {
|
||||
Fetching,
|
||||
FetchFailed,
|
||||
Fetched,
|
||||
}
|
||||
|
||||
export type StakeProgramData = {
|
||||
name: "stake";
|
||||
parsed: StakeAccount | StakeAccountWasm;
|
||||
|
@ -37,98 +33,12 @@ export interface Details {
|
|||
|
||||
export interface Account {
|
||||
pubkey: PublicKey;
|
||||
status: FetchStatus;
|
||||
lamports?: number;
|
||||
lamports: number;
|
||||
details?: Details;
|
||||
}
|
||||
|
||||
type Accounts = { [address: string]: Account };
|
||||
interface State {
|
||||
accounts: Accounts;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export enum ActionType {
|
||||
Update,
|
||||
Fetch,
|
||||
Clear,
|
||||
}
|
||||
|
||||
interface Update {
|
||||
type: ActionType.Update;
|
||||
url: string;
|
||||
pubkey: PublicKey;
|
||||
data: {
|
||||
status: FetchStatus;
|
||||
lamports?: number;
|
||||
details?: Details;
|
||||
};
|
||||
}
|
||||
|
||||
interface Fetch {
|
||||
type: ActionType.Fetch;
|
||||
url: string;
|
||||
pubkey: PublicKey;
|
||||
}
|
||||
|
||||
interface Clear {
|
||||
type: ActionType.Clear;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Action = Update | Fetch | Clear;
|
||||
type Dispatch = (action: Action) => void;
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
if (action.type === ActionType.Clear) {
|
||||
return { url: action.url, accounts: {} };
|
||||
} else if (action.url !== state.url) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ActionType.Fetch: {
|
||||
const address = action.pubkey.toBase58();
|
||||
const account = state.accounts[address];
|
||||
if (account) {
|
||||
const accounts = {
|
||||
...state.accounts,
|
||||
[address]: {
|
||||
pubkey: account.pubkey,
|
||||
status: FetchStatus.Fetching,
|
||||
},
|
||||
};
|
||||
return { ...state, accounts };
|
||||
} else {
|
||||
const accounts = {
|
||||
...state.accounts,
|
||||
[address]: {
|
||||
status: FetchStatus.Fetching,
|
||||
pubkey: action.pubkey,
|
||||
},
|
||||
};
|
||||
return { ...state, accounts };
|
||||
}
|
||||
}
|
||||
|
||||
case ActionType.Update: {
|
||||
const address = action.pubkey.toBase58();
|
||||
const account = state.accounts[address];
|
||||
if (account) {
|
||||
const accounts = {
|
||||
...state.accounts,
|
||||
[address]: {
|
||||
...account,
|
||||
...action.data,
|
||||
},
|
||||
};
|
||||
return { ...state, accounts };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
type State = Cache.State<Account>;
|
||||
type Dispatch = Cache.Dispatch<Account>;
|
||||
|
||||
const StateContext = React.createContext<State | undefined>(undefined);
|
||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||
|
@ -136,15 +46,12 @@ const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
|||
type AccountsProviderProps = { children: React.ReactNode };
|
||||
export function AccountsProvider({ children }: AccountsProviderProps) {
|
||||
const { url } = useCluster();
|
||||
const [state, dispatch] = React.useReducer(reducer, {
|
||||
url,
|
||||
accounts: {},
|
||||
});
|
||||
const [state, dispatch] = Cache.useReducer<Account>(url);
|
||||
|
||||
// Clear account statuses whenever cluster is changed
|
||||
// Clear accounts cache whenever cluster is changed
|
||||
React.useEffect(() => {
|
||||
dispatch({ type: ActionType.Clear, url });
|
||||
}, [url]);
|
||||
}, [dispatch, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
|
@ -163,18 +70,20 @@ async function fetchAccountInfo(
|
|||
url: string
|
||||
) {
|
||||
dispatch({
|
||||
type: ActionType.Fetch,
|
||||
pubkey,
|
||||
type: ActionType.Update,
|
||||
key: pubkey.toBase58(),
|
||||
status: Cache.FetchStatus.Fetching,
|
||||
url,
|
||||
});
|
||||
|
||||
let data;
|
||||
let fetchStatus;
|
||||
let details;
|
||||
let lamports;
|
||||
try {
|
||||
const result = (
|
||||
await new Connection(url, "single").getParsedAccountInfo(pubkey)
|
||||
).value;
|
||||
|
||||
let lamports, details;
|
||||
if (result === null) {
|
||||
lamports = 0;
|
||||
} else {
|
||||
|
@ -227,13 +136,19 @@ async function fetchAccountInfo(
|
|||
data,
|
||||
};
|
||||
}
|
||||
data = { pubkey, lamports, details };
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch account info", error);
|
||||
fetchStatus = FetchStatus.FetchFailed;
|
||||
}
|
||||
const data = { status: fetchStatus, lamports, details };
|
||||
dispatch({ type: ActionType.Update, data, pubkey, url });
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
status: fetchStatus,
|
||||
data,
|
||||
key: pubkey.toBase58(),
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAccounts() {
|
||||
|
@ -241,17 +156,19 @@ export function useAccounts() {
|
|||
if (!context) {
|
||||
throw new Error(`useAccounts must be used within a AccountsProvider`);
|
||||
}
|
||||
return context.accounts;
|
||||
return context.entries;
|
||||
}
|
||||
|
||||
export function useAccountInfo(address: string) {
|
||||
export function useAccountInfo(
|
||||
address: string
|
||||
): Cache.CacheEntry<Account> | undefined {
|
||||
const context = React.useContext(StateContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`useAccountInfo must be used within a AccountsProvider`);
|
||||
}
|
||||
|
||||
return context.accounts[address];
|
||||
return context.entries[address];
|
||||
}
|
||||
|
||||
export function useFetchAccountInfo() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { FetchStatus } from "./index";
|
||||
import * as Cache from "providers/cache";
|
||||
import { ActionType, FetchStatus } from "providers/cache";
|
||||
import { TokenAccountInfo } from "validators/accounts/token";
|
||||
import { useCluster } from "../cluster";
|
||||
import { coerce } from "superstruct";
|
||||
|
@ -11,63 +12,11 @@ export type TokenInfoWithPubkey = {
|
|||
};
|
||||
|
||||
interface AccountTokens {
|
||||
status: FetchStatus;
|
||||
tokens?: TokenInfoWithPubkey[];
|
||||
}
|
||||
|
||||
interface Update {
|
||||
type: "update";
|
||||
url: string;
|
||||
pubkey: PublicKey;
|
||||
status: FetchStatus;
|
||||
tokens?: TokenInfoWithPubkey[];
|
||||
}
|
||||
|
||||
interface Clear {
|
||||
type: "clear";
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Action = Update | Clear;
|
||||
type State = {
|
||||
url: string;
|
||||
map: { [address: string]: AccountTokens };
|
||||
};
|
||||
|
||||
type Dispatch = (action: Action) => void;
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
if (action.type === "clear") {
|
||||
return {
|
||||
url: action.url,
|
||||
map: {},
|
||||
};
|
||||
} else if (action.url !== state.url) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const address = action.pubkey.toBase58();
|
||||
let addressEntry = state.map[address];
|
||||
if (addressEntry && action.status === FetchStatus.Fetching) {
|
||||
addressEntry = {
|
||||
...addressEntry,
|
||||
status: FetchStatus.Fetching,
|
||||
};
|
||||
} else {
|
||||
addressEntry = {
|
||||
tokens: action.tokens,
|
||||
status: action.status,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
[address]: addressEntry,
|
||||
},
|
||||
};
|
||||
}
|
||||
type State = Cache.State<AccountTokens>;
|
||||
type Dispatch = Cache.Dispatch<AccountTokens>;
|
||||
|
||||
const StateContext = React.createContext<State | undefined>(undefined);
|
||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||
|
@ -75,11 +24,11 @@ const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
|||
type ProviderProps = { children: React.ReactNode };
|
||||
export function TokensProvider({ children }: ProviderProps) {
|
||||
const { url } = useCluster();
|
||||
const [state, dispatch] = React.useReducer(reducer, { url, map: {} });
|
||||
const [state, dispatch] = Cache.useReducer<AccountTokens>(url);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch({ url, type: "clear" });
|
||||
}, [url]);
|
||||
dispatch({ url, type: ActionType.Clear });
|
||||
}, [dispatch, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
|
@ -99,33 +48,38 @@ async function fetchAccountTokens(
|
|||
pubkey: PublicKey,
|
||||
url: string
|
||||
) {
|
||||
const key = pubkey.toBase58();
|
||||
dispatch({
|
||||
type: "update",
|
||||
type: ActionType.Update,
|
||||
key,
|
||||
status: FetchStatus.Fetching,
|
||||
pubkey,
|
||||
url,
|
||||
});
|
||||
|
||||
let status;
|
||||
let tokens;
|
||||
let data;
|
||||
try {
|
||||
const { value } = await new Connection(
|
||||
url,
|
||||
"recent"
|
||||
).getParsedTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID });
|
||||
tokens = value.map((accountInfo) => {
|
||||
const parsedInfo = accountInfo.account.data.parsed.info;
|
||||
const info = coerce(parsedInfo, TokenAccountInfo);
|
||||
return { info, pubkey: accountInfo.pubkey };
|
||||
});
|
||||
data = {
|
||||
tokens: value.map((accountInfo) => {
|
||||
const parsedInfo = accountInfo.account.data.parsed.info;
|
||||
const info = coerce(parsedInfo, TokenAccountInfo);
|
||||
return { info, pubkey: accountInfo.pubkey };
|
||||
}),
|
||||
};
|
||||
status = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
status = FetchStatus.FetchFailed;
|
||||
}
|
||||
dispatch({ type: "update", url, status, tokens, pubkey });
|
||||
dispatch({ type: ActionType.Update, url, status, data, key });
|
||||
}
|
||||
|
||||
export function useAccountOwnedTokens(address: string) {
|
||||
export function useAccountOwnedTokens(
|
||||
address: string
|
||||
): Cache.CacheEntry<AccountTokens> | undefined {
|
||||
const context = React.useContext(StateContext);
|
||||
|
||||
if (!context) {
|
||||
|
@ -134,7 +88,7 @@ export function useAccountOwnedTokens(address: string) {
|
|||
);
|
||||
}
|
||||
|
||||
return context.map[address];
|
||||
return context.entries[address];
|
||||
}
|
||||
|
||||
export function useFetchAccountOwnedTokens() {
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
import React from "react";
|
||||
|
||||
export enum FetchStatus {
|
||||
Fetching,
|
||||
FetchFailed,
|
||||
Fetched,
|
||||
}
|
||||
|
||||
export type CacheEntry<T> = {
|
||||
status: FetchStatus;
|
||||
data?: T;
|
||||
};
|
||||
|
||||
export type State<T> = {
|
||||
entries: {
|
||||
[key: string]: CacheEntry<T>;
|
||||
};
|
||||
url: string;
|
||||
};
|
||||
|
||||
export enum ActionType {
|
||||
Update,
|
||||
Clear,
|
||||
}
|
||||
|
||||
export type Update<T> = {
|
||||
type: ActionType.Update;
|
||||
url: string;
|
||||
key: string;
|
||||
status: FetchStatus;
|
||||
data?: T;
|
||||
};
|
||||
|
||||
export type Clear = {
|
||||
type: ActionType.Clear;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type Action<T> = Update<T> | Clear;
|
||||
export type Dispatch<T> = (action: Action<T>) => void;
|
||||
type Reducer<T, U> = (state: State<T>, action: Action<U>) => State<T>;
|
||||
type Reconciler<T, U> = (
|
||||
entry: T | undefined,
|
||||
update: U | undefined
|
||||
) => T | undefined;
|
||||
|
||||
function defaultReconciler<T>(entry: T | undefined, update: T | undefined) {
|
||||
if (entry) {
|
||||
if (update) {
|
||||
return {
|
||||
...entry,
|
||||
...update,
|
||||
};
|
||||
} else {
|
||||
return entry;
|
||||
}
|
||||
} else {
|
||||
return update;
|
||||
}
|
||||
}
|
||||
|
||||
function defaultReducer<T>(state: State<T>, action: Action<T>) {
|
||||
return reducer(state, action, defaultReconciler);
|
||||
}
|
||||
|
||||
export function useReducer<T>(url: string) {
|
||||
return React.useReducer<Reducer<T, T>>(defaultReducer, { url, entries: {} });
|
||||
}
|
||||
|
||||
export function useCustomReducer<T, U>(
|
||||
url: string,
|
||||
reconciler: Reconciler<T, U>
|
||||
) {
|
||||
const customReducer = React.useMemo(() => {
|
||||
return (state: State<T>, action: Action<U>) => {
|
||||
return reducer(state, action, reconciler);
|
||||
};
|
||||
}, [reconciler]);
|
||||
return React.useReducer<Reducer<T, U>>(customReducer, { url, entries: {} });
|
||||
}
|
||||
|
||||
export function reducer<T, U>(
|
||||
state: State<T>,
|
||||
action: Action<U>,
|
||||
reconciler: Reconciler<T, U>
|
||||
): State<T> {
|
||||
if (action.type === ActionType.Clear) {
|
||||
return { url: action.url, entries: {} };
|
||||
} else if (action.url !== state.url) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ActionType.Update: {
|
||||
const key = action.key;
|
||||
const entry = state.entries[key];
|
||||
const entries = {
|
||||
...state.entries,
|
||||
[key]: {
|
||||
...entry,
|
||||
status: action.status,
|
||||
data: reconciler(entry?.data, action.data),
|
||||
},
|
||||
};
|
||||
return { ...state, entries };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,78 +5,16 @@ import {
|
|||
ParsedConfirmedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster } from "../cluster";
|
||||
import { FetchStatus } from "./index";
|
||||
import { CACHED_DETAILS, isCached } from "./cached";
|
||||
import * as Cache from "providers/cache";
|
||||
import { ActionType, FetchStatus } from "providers/cache";
|
||||
|
||||
export interface Details {
|
||||
fetchStatus: FetchStatus;
|
||||
transaction: ParsedConfirmedTransaction | null;
|
||||
transaction?: ParsedConfirmedTransaction | null;
|
||||
}
|
||||
|
||||
type State = {
|
||||
entries: { [signature: string]: Details };
|
||||
url: string;
|
||||
};
|
||||
|
||||
export enum ActionType {
|
||||
Update,
|
||||
Clear,
|
||||
}
|
||||
|
||||
interface Update {
|
||||
type: ActionType.Update;
|
||||
url: string;
|
||||
signature: string;
|
||||
fetchStatus: FetchStatus;
|
||||
transaction: ParsedConfirmedTransaction | null;
|
||||
}
|
||||
|
||||
interface Clear {
|
||||
type: ActionType.Clear;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Action = Update | Clear;
|
||||
type Dispatch = (action: Action) => void;
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
if (action.type === ActionType.Clear) {
|
||||
return { url: action.url, entries: {} };
|
||||
} else if (action.url !== state.url) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ActionType.Update: {
|
||||
const signature = action.signature;
|
||||
const details = state.entries[signature];
|
||||
if (details) {
|
||||
return {
|
||||
...state,
|
||||
entries: {
|
||||
...state.entries,
|
||||
[signature]: {
|
||||
...details,
|
||||
fetchStatus: action.fetchStatus,
|
||||
transaction: action.transaction,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
entries: {
|
||||
...state.entries,
|
||||
[signature]: {
|
||||
fetchStatus: FetchStatus.Fetching,
|
||||
transaction: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type State = Cache.State<Details>;
|
||||
type Dispatch = Cache.Dispatch<Details>;
|
||||
|
||||
export const StateContext = React.createContext<State | undefined>(undefined);
|
||||
export const DispatchContext = React.createContext<Dispatch | undefined>(
|
||||
|
@ -86,11 +24,11 @@ export const DispatchContext = React.createContext<Dispatch | undefined>(
|
|||
type DetailsProviderProps = { children: React.ReactNode };
|
||||
export function DetailsProvider({ children }: DetailsProviderProps) {
|
||||
const { url } = useCluster();
|
||||
const [state, dispatch] = React.useReducer(reducer, { url, entries: {} });
|
||||
const [state, dispatch] = Cache.useReducer<Details>(url);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch({ type: ActionType.Clear, url });
|
||||
}, [url]);
|
||||
}, [dispatch, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
|
@ -108,14 +46,13 @@ async function fetchDetails(
|
|||
) {
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
fetchStatus: FetchStatus.Fetching,
|
||||
transaction: null,
|
||||
signature,
|
||||
status: FetchStatus.Fetching,
|
||||
key: signature,
|
||||
url,
|
||||
});
|
||||
|
||||
let fetchStatus;
|
||||
let transaction = null;
|
||||
let transaction;
|
||||
if (isCached(url, signature)) {
|
||||
transaction = CACHED_DETAILS[signature];
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
|
@ -132,9 +69,9 @@ async function fetchDetails(
|
|||
}
|
||||
dispatch({
|
||||
type: ActionType.Update,
|
||||
fetchStatus,
|
||||
signature,
|
||||
transaction,
|
||||
status: fetchStatus,
|
||||
key: signature,
|
||||
data: { transaction },
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
@ -152,3 +89,17 @@ export function useFetchTransactionDetails() {
|
|||
url && fetchDetails(dispatch, signature, url);
|
||||
};
|
||||
}
|
||||
|
||||
export function useTransactionDetails(
|
||||
signature: TransactionSignature
|
||||
): Cache.CacheEntry<Details> | undefined {
|
||||
const context = React.useContext(StateContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`useTransactionDetails must be used within a TransactionsProvider`
|
||||
);
|
||||
}
|
||||
|
||||
return context.entries[signature];
|
||||
}
|
||||
|
|
|
@ -2,27 +2,14 @@ import React from "react";
|
|||
import {
|
||||
TransactionSignature,
|
||||
Connection,
|
||||
SystemProgram,
|
||||
Account,
|
||||
SignatureResult,
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { useQuery } from "utils/url";
|
||||
import { useCluster, Cluster, ClusterStatus } from "../cluster";
|
||||
import {
|
||||
DetailsProvider,
|
||||
StateContext as DetailsStateContext,
|
||||
} from "./details";
|
||||
import base58 from "bs58";
|
||||
import { useFetchAccountInfo } from "../accounts";
|
||||
import { useCluster } from "../cluster";
|
||||
import { DetailsProvider } from "./details";
|
||||
import * as Cache from "providers/cache";
|
||||
import { ActionType, FetchStatus } from "providers/cache";
|
||||
import { CACHED_STATUSES, isCached } from "./cached";
|
||||
|
||||
export enum FetchStatus {
|
||||
Fetching,
|
||||
FetchFailed,
|
||||
Fetched,
|
||||
}
|
||||
export { useTransactionDetails } from "./details";
|
||||
|
||||
export type Confirmations = number | "max";
|
||||
|
||||
|
@ -36,125 +23,27 @@ export interface TransactionStatusInfo {
|
|||
}
|
||||
|
||||
export interface TransactionStatus {
|
||||
fetchStatus: FetchStatus;
|
||||
signature: TransactionSignature;
|
||||
info?: TransactionStatusInfo;
|
||||
}
|
||||
|
||||
type Transactions = { [signature: string]: TransactionStatus };
|
||||
interface State {
|
||||
transactions: Transactions;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export enum ActionType {
|
||||
UpdateStatus,
|
||||
FetchSignature,
|
||||
Clear,
|
||||
}
|
||||
|
||||
interface UpdateStatus {
|
||||
type: ActionType.UpdateStatus;
|
||||
url: string;
|
||||
signature: TransactionSignature;
|
||||
fetchStatus: FetchStatus;
|
||||
info?: TransactionStatusInfo;
|
||||
}
|
||||
|
||||
interface FetchSignature {
|
||||
type: ActionType.FetchSignature;
|
||||
url: string;
|
||||
signature: TransactionSignature;
|
||||
}
|
||||
|
||||
interface Clear {
|
||||
type: ActionType.Clear;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type Action = UpdateStatus | FetchSignature | Clear;
|
||||
type Dispatch = (action: Action) => void;
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
if (action.type === ActionType.Clear) {
|
||||
return { url: action.url, transactions: {} };
|
||||
} else if (action.url !== state.url) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ActionType.FetchSignature: {
|
||||
const signature = action.signature;
|
||||
const transaction = state.transactions[signature];
|
||||
if (transaction) {
|
||||
const transactions = {
|
||||
...state.transactions,
|
||||
[action.signature]: {
|
||||
...transaction,
|
||||
fetchStatus: FetchStatus.Fetching,
|
||||
info: undefined,
|
||||
},
|
||||
};
|
||||
return { ...state, transactions };
|
||||
} else {
|
||||
const transactions = {
|
||||
...state.transactions,
|
||||
[action.signature]: {
|
||||
signature: action.signature,
|
||||
fetchStatus: FetchStatus.Fetching,
|
||||
},
|
||||
};
|
||||
return { ...state, transactions };
|
||||
}
|
||||
}
|
||||
|
||||
case ActionType.UpdateStatus: {
|
||||
const transaction = state.transactions[action.signature];
|
||||
if (transaction) {
|
||||
const transactions = {
|
||||
...state.transactions,
|
||||
[action.signature]: {
|
||||
...transaction,
|
||||
fetchStatus: action.fetchStatus,
|
||||
info: action.info,
|
||||
},
|
||||
};
|
||||
return { ...state, transactions };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
info: TransactionStatusInfo | null;
|
||||
}
|
||||
|
||||
export const TX_ALIASES = ["tx", "txn", "transaction"];
|
||||
|
||||
type State = Cache.State<TransactionStatus>;
|
||||
type Dispatch = Cache.Dispatch<TransactionStatus>;
|
||||
|
||||
const StateContext = React.createContext<State | undefined>(undefined);
|
||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||
|
||||
type TransactionsProviderProps = { children: React.ReactNode };
|
||||
export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||
const { cluster, status: clusterStatus, url } = useCluster();
|
||||
const [state, dispatch] = React.useReducer(reducer, {
|
||||
transactions: {},
|
||||
url,
|
||||
});
|
||||
const { url } = useCluster();
|
||||
const [state, dispatch] = Cache.useReducer<TransactionStatus>(url);
|
||||
|
||||
const fetchAccount = useFetchAccountInfo();
|
||||
const query = useQuery();
|
||||
const testFlag = query.get("test");
|
||||
|
||||
// Check transaction statuses whenever cluster updates
|
||||
// Clear accounts cache whenever cluster is changed
|
||||
React.useEffect(() => {
|
||||
if (clusterStatus === ClusterStatus.Connecting) {
|
||||
dispatch({ type: ActionType.Clear, url });
|
||||
}
|
||||
|
||||
// Create a test transaction
|
||||
if (cluster === Cluster.Devnet && testFlag !== null) {
|
||||
createTestTransaction(dispatch, fetchAccount, url, clusterStatus);
|
||||
}
|
||||
}, [testFlag, cluster, clusterStatus, url]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
dispatch({ type: ActionType.Clear, url });
|
||||
}, [dispatch, url]);
|
||||
|
||||
return (
|
||||
<StateContext.Provider value={state}>
|
||||
|
@ -165,64 +54,23 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
|||
);
|
||||
}
|
||||
|
||||
async function createTestTransaction(
|
||||
dispatch: Dispatch,
|
||||
fetchAccount: (pubkey: PublicKey) => void,
|
||||
url: string,
|
||||
clusterStatus: ClusterStatus
|
||||
) {
|
||||
const testKey = process.env.REACT_APP_TEST_KEY;
|
||||
let testAccount = new Account();
|
||||
if (testKey) {
|
||||
testAccount = new Account(base58.decode(testKey));
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = new Connection(url, "recent");
|
||||
const signature = await connection.requestAirdrop(
|
||||
testAccount.publicKey,
|
||||
100000
|
||||
);
|
||||
fetchTransactionStatus(dispatch, signature, url);
|
||||
fetchAccount(testAccount.publicKey);
|
||||
} catch (error) {
|
||||
console.error("Failed to create test success transaction", error);
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = new Connection(url, "recent");
|
||||
const tx = SystemProgram.transfer({
|
||||
fromPubkey: testAccount.publicKey,
|
||||
toPubkey: testAccount.publicKey,
|
||||
lamports: 1,
|
||||
});
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
connection,
|
||||
tx,
|
||||
[testAccount],
|
||||
{ confirmations: 1, skipPreflight: false }
|
||||
);
|
||||
fetchTransactionStatus(dispatch, signature, url);
|
||||
} catch (error) {
|
||||
console.error("Failed to create test failure transaction", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchTransactionStatus(
|
||||
dispatch: Dispatch,
|
||||
signature: TransactionSignature,
|
||||
url: string
|
||||
) {
|
||||
dispatch({
|
||||
type: ActionType.FetchSignature,
|
||||
signature,
|
||||
type: ActionType.Update,
|
||||
key: signature,
|
||||
status: FetchStatus.Fetching,
|
||||
url,
|
||||
});
|
||||
|
||||
let fetchStatus;
|
||||
let info: TransactionStatusInfo | undefined;
|
||||
let data;
|
||||
if (isCached(url, signature)) {
|
||||
info = CACHED_STATUSES[signature];
|
||||
const info = CACHED_STATUSES[signature];
|
||||
data = { signature, info };
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} else {
|
||||
try {
|
||||
|
@ -231,6 +79,7 @@ export async function fetchTransactionStatus(
|
|||
searchTransactionHistory: true,
|
||||
});
|
||||
|
||||
let info = null;
|
||||
if (value !== null) {
|
||||
let blockTime = null;
|
||||
try {
|
||||
|
@ -260,6 +109,7 @@ export async function fetchTransactionStatus(
|
|||
result: { err: value.err },
|
||||
};
|
||||
}
|
||||
data = { signature, info };
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch transaction status", error);
|
||||
|
@ -268,10 +118,10 @@ export async function fetchTransactionStatus(
|
|||
}
|
||||
|
||||
dispatch({
|
||||
type: ActionType.UpdateStatus,
|
||||
signature,
|
||||
fetchStatus,
|
||||
info,
|
||||
type: ActionType.Update,
|
||||
key: signature,
|
||||
status: fetchStatus,
|
||||
data,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
@ -286,7 +136,9 @@ export function useTransactions() {
|
|||
return context;
|
||||
}
|
||||
|
||||
export function useTransactionStatus(signature: TransactionSignature) {
|
||||
export function useTransactionStatus(
|
||||
signature: TransactionSignature
|
||||
): Cache.CacheEntry<TransactionStatus> | undefined {
|
||||
const context = React.useContext(StateContext);
|
||||
|
||||
if (!context) {
|
||||
|
@ -295,18 +147,6 @@ export function useTransactionStatus(signature: TransactionSignature) {
|
|||
);
|
||||
}
|
||||
|
||||
return context.transactions[signature];
|
||||
}
|
||||
|
||||
export function useTransactionDetails(signature: TransactionSignature) {
|
||||
const context = React.useContext(DetailsStateContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`useTransactionDetails must be used within a TransactionsProvider`
|
||||
);
|
||||
}
|
||||
|
||||
return context.entries[signature];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue