@@ -49,10 +42,16 @@ export default function AccountDetails({ address }: Props) {
}
function AccountCards({ pubkey }: { pubkey: PublicKey }) {
+ const fetchAccount = useFetchAccountInfo();
const address = pubkey.toBase58();
const info = useAccountInfo(address);
const refresh = useFetchAccountInfo();
+ // Fetch account on load
+ React.useEffect(() => {
+ if (pubkey && !info) fetchAccount(pubkey);
+ }, [address]); // eslint-disable-line react-hooks/exhaustive-deps
+
if (!info || info.status === FetchStatus.Fetching) {
return
;
} else if (
diff --git a/explorer/src/components/TransactionDetails.tsx b/explorer/src/components/TransactionDetails.tsx
index a528ba83b9..cd84cce015 100644
--- a/explorer/src/components/TransactionDetails.tsx
+++ b/explorer/src/components/TransactionDetails.tsx
@@ -28,13 +28,6 @@ import { isCached } from "providers/transactions/cached";
type Props = { signature: TransactionSignature };
export default function TransactionDetails({ signature }: Props) {
- const fetchTransaction = useFetchTransactionStatus();
-
- // Fetch transaction on load
- React.useEffect(() => {
- fetchTransaction(signature);
- }, [signature]); // eslint-disable-line react-hooks/exhaustive-deps
-
return (
@@ -52,11 +45,17 @@ export default function TransactionDetails({ signature }: Props) {
}
function StatusCard({ signature }: Props) {
+ const fetchStatus = useFetchTransactionStatus();
const status = useTransactionStatus(signature);
const refresh = useFetchTransactionStatus();
const details = useTransactionDetails(signature);
const { firstAvailableBlock } = useCluster();
+ // Fetch transaction on load
+ React.useEffect(() => {
+ if (!status) fetchStatus(signature);
+ }, [signature]); // eslint-disable-line react-hooks/exhaustive-deps
+
if (!status || status.fetchStatus === FetchStatus.Fetching) {
return ;
} else if (status?.fetchStatus === FetchStatus.FetchFailed) {
diff --git a/explorer/src/providers/accounts/history.tsx b/explorer/src/providers/accounts/history.tsx
index 13b90e6c23..1bc699dfff 100644
--- a/explorer/src/providers/accounts/history.tsx
+++ b/explorer/src/providers/accounts/history.tsx
@@ -19,7 +19,7 @@ type State = { [address: string]: AccountHistory };
export enum ActionType {
Update,
Add,
- Remove,
+ Clear,
}
interface Update {
@@ -32,38 +32,26 @@ interface Update {
interface Add {
type: ActionType.Add;
- addresses: string[];
+ address: string;
}
-interface Remove {
- type: ActionType.Remove;
- addresses: string[];
+interface Clear {
+ type: ActionType.Clear;
}
-type Action = Update | Add | Remove;
+type Action = Update | Add | Clear;
type Dispatch = (action: Action) => void;
function reducer(state: State, action: Action): State {
switch (action.type) {
case ActionType.Add: {
- if (action.addresses.length === 0) return state;
const details = { ...state };
- action.addresses.forEach((address) => {
- if (!details[address]) {
- details[address] = {
- status: FetchStatus.Fetching,
- };
- }
- });
- return details;
- }
-
- case ActionType.Remove: {
- if (action.addresses.length === 0) return state;
- const details = { ...state };
- action.addresses.forEach((address) => {
- delete details[address];
- });
+ const address = action.address;
+ if (!details[address]) {
+ details[address] = {
+ status: FetchStatus.Fetching,
+ };
+ }
return details;
}
@@ -87,6 +75,10 @@ function reducer(state: State, action: Action): State {
}
break;
}
+
+ case ActionType.Clear: {
+ return {};
+ }
}
return state;
}
@@ -100,45 +92,33 @@ const DispatchContext = React.createContext(undefined);
type HistoryProviderProps = { children: React.ReactNode };
export function HistoryProvider({ children }: HistoryProviderProps) {
const [state, dispatch] = React.useReducer(reducer, {});
- const { accounts } = useAccounts();
+ const { accounts, lastFetchedAddress } = useAccounts();
const { url } = useCluster();
const manager = React.useRef(new HistoryManager(url));
React.useEffect(() => {
manager.current = new HistoryManager(url);
+ dispatch({ type: ActionType.Clear });
}, [url]);
// Fetch history for new accounts
React.useEffect(() => {
- const removeAddresses = new Set();
- const fetchAddresses = new Set();
- accounts.forEach(({ pubkey, lamports }) => {
- const address = pubkey.toBase58();
- if (lamports !== undefined && !state[address])
- fetchAddresses.add(address);
- else if (lamports === undefined && state[address])
- removeAddresses.add(address);
- });
-
- const removeList: string[] = [];
- removeAddresses.forEach((address) => {
- manager.current.removeAccountHistory(address);
- removeList.push(address);
- });
- dispatch({ type: ActionType.Remove, addresses: removeList });
-
- const fetchList: string[] = [];
- fetchAddresses.forEach((s) => fetchList.push(s));
- dispatch({ type: ActionType.Add, addresses: fetchList });
- fetchAddresses.forEach((address) => {
- fetchAccountHistory(
- dispatch,
- new PublicKey(address),
- manager.current,
- true
- );
- });
- }, [accounts]); // eslint-disable-line react-hooks/exhaustive-deps
+ if (lastFetchedAddress) {
+ const infoFetched =
+ accounts[lastFetchedAddress] &&
+ accounts[lastFetchedAddress].lamports !== undefined;
+ const noHistory = !state[lastFetchedAddress];
+ if (infoFetched && noHistory) {
+ dispatch({ type: ActionType.Add, address: lastFetchedAddress });
+ fetchAccountHistory(
+ dispatch,
+ new PublicKey(lastFetchedAddress),
+ manager.current,
+ true
+ );
+ }
+ }
+ }, [accounts, lastFetchedAddress]); // eslint-disable-line react-hooks/exhaustive-deps
return (
diff --git a/explorer/src/providers/accounts/index.tsx b/explorer/src/providers/accounts/index.tsx
index 6a56ac4c40..fc3defc2a1 100644
--- a/explorer/src/providers/accounts/index.tsx
+++ b/explorer/src/providers/accounts/index.tsx
@@ -1,7 +1,6 @@
import React from "react";
import { StakeAccount } from "solana-sdk-wasm";
import { PublicKey, Connection, StakeProgram } from "@solana/web3.js";
-import { useQuery } from "../../utils/url";
import { useCluster, ClusterStatus } from "../cluster";
import { HistoryProvider } from "./history";
export { useAccountHistory } from "./history";
@@ -20,7 +19,6 @@ export interface Details {
}
export interface Account {
- id: number;
pubkey: PublicKey;
status: FetchStatus;
lamports?: number;
@@ -29,13 +27,14 @@ export interface Account {
type Accounts = { [address: string]: Account };
interface State {
- idCounter: number;
accounts: Accounts;
+ lastFetchedAddress: string | undefined;
}
export enum ActionType {
Update,
Fetch,
+ Clear,
}
interface Update {
@@ -53,7 +52,11 @@ interface Fetch {
pubkey: PublicKey;
}
-type Action = Update | Fetch;
+interface Clear {
+ type: ActionType.Clear;
+}
+
+type Action = Update | Fetch | Clear;
type Dispatch = (action: Action) => void;
function reducer(state: State, action: Action): State {
@@ -65,23 +68,20 @@ function reducer(state: State, action: Action): State {
const accounts = {
...state.accounts,
[address]: {
- id: account.id,
pubkey: account.pubkey,
status: FetchStatus.Fetching,
},
};
- return { ...state, accounts };
+ return { ...state, accounts, lastFetchedAddress: address };
} else {
- const idCounter = state.idCounter + 1;
const accounts = {
...state.accounts,
[address]: {
- id: idCounter,
status: FetchStatus.Fetching,
pubkey: action.pubkey,
},
};
- return { ...state, accounts, idCounter };
+ return { ...state, accounts, lastFetchedAddress: address };
}
}
@@ -100,6 +100,13 @@ function reducer(state: State, action: Action): State {
}
break;
}
+
+ case ActionType.Clear: {
+ return {
+ ...state,
+ accounts: {},
+ };
+ }
}
return state;
}
@@ -113,40 +120,20 @@ const DispatchContext = React.createContext(undefined);
type AccountsProviderProps = { children: React.ReactNode };
export function AccountsProvider({ children }: AccountsProviderProps) {
const [state, dispatch] = React.useReducer(reducer, {
- idCounter: 0,
accounts: {},
+ lastFetchedAddress: undefined,
});
- const { status, url } = useCluster();
-
// Check account statuses on startup and whenever cluster updates
+ const { status, url } = useCluster();
React.useEffect(() => {
- Object.keys(state.accounts).forEach((address) => {
- fetchAccountInfo(dispatch, new PublicKey(address), url, status);
- });
+ if (status === ClusterStatus.Connecting) {
+ dispatch({ type: ActionType.Clear });
+ } else if (status === ClusterStatus.Connected && state.lastFetchedAddress) {
+ fetchAccountInfo(dispatch, new PublicKey(state.lastFetchedAddress), url);
+ }
}, [status, url]); // eslint-disable-line react-hooks/exhaustive-deps
- const query = useQuery();
- const values = ACCOUNT_ALIASES.concat(ACCOUNT_ALIASES_PLURAL).map((key) =>
- query.get(key)
- );
- React.useEffect(() => {
- values
- .filter((value): value is string => value !== null)
- .flatMap((value) => value.split(","))
- // Remove duplicates
- .filter((item, pos, self) => self.indexOf(item) === pos)
- .filter((address) => !state.accounts[address])
- .forEach((address) => {
- try {
- fetchAccountInfo(dispatch, new PublicKey(address), url, status);
- } catch (err) {
- console.error(err);
- // TODO handle bad addresses
- }
- });
- }, [values.toString()]); // eslint-disable-line react-hooks/exhaustive-deps
-
return (
@@ -159,17 +146,13 @@ export function AccountsProvider({ children }: AccountsProviderProps) {
async function fetchAccountInfo(
dispatch: Dispatch,
pubkey: PublicKey,
- url: string,
- status: ClusterStatus
+ url: string
) {
dispatch({
type: ActionType.Fetch,
pubkey,
});
- // We will auto-refetch when status is no longer connecting
- if (status === ClusterStatus.Connecting) return;
-
let fetchStatus;
let details;
let lamports;
@@ -213,12 +196,7 @@ export function useAccounts() {
if (!context) {
throw new Error(`useAccounts must be used within a AccountsProvider`);
}
- return {
- idCounter: context.idCounter,
- accounts: Object.values(context.accounts).sort((a, b) =>
- a.id <= b.id ? 1 : -1
- ),
- };
+ return context;
}
export function useAccountInfo(address: string) {
@@ -239,8 +217,8 @@ export function useFetchAccountInfo() {
);
}
- const { url, status } = useCluster();
+ const { url } = useCluster();
return (pubkey: PublicKey) => {
- fetchAccountInfo(dispatch, pubkey, url, status);
+ fetchAccountInfo(dispatch, pubkey, url);
};
}
diff --git a/explorer/src/providers/transactions/details.tsx b/explorer/src/providers/transactions/details.tsx
index 030a7f1d16..7abce55053 100644
--- a/explorer/src/providers/transactions/details.tsx
+++ b/explorer/src/providers/transactions/details.tsx
@@ -18,7 +18,7 @@ type State = { [signature: string]: Details };
export enum ActionType {
Update,
Add,
- Remove,
+ Clear,
}
interface Update {
@@ -30,39 +30,27 @@ interface Update {
interface Add {
type: ActionType.Add;
- signatures: TransactionSignature[];
+ signature: TransactionSignature;
}
-interface Remove {
- type: ActionType.Remove;
- signatures: TransactionSignature[];
+interface Clear {
+ type: ActionType.Clear;
}
-type Action = Update | Add | Remove;
+type Action = Update | Add | Clear;
type Dispatch = (action: Action) => void;
function reducer(state: State, action: Action): State {
switch (action.type) {
case ActionType.Add: {
- if (action.signatures.length === 0) return state;
const details = { ...state };
- action.signatures.forEach((signature) => {
- if (!details[signature]) {
- details[signature] = {
- fetchStatus: FetchStatus.Fetching,
- transaction: null,
- };
- }
- });
- return details;
- }
-
- case ActionType.Remove: {
- if (action.signatures.length === 0) return state;
- const details = { ...state };
- action.signatures.forEach((signature) => {
- delete details[signature];
- });
+ const signature = action.signature;
+ if (!details[signature]) {
+ details[signature] = {
+ fetchStatus: FetchStatus.Fetching,
+ transaction: null,
+ };
+ }
return details;
}
@@ -81,6 +69,10 @@ function reducer(state: State, action: Action): State {
}
break;
}
+
+ case ActionType.Clear: {
+ return {};
+ }
}
return state;
}
@@ -93,32 +85,26 @@ export const DispatchContext = React.createContext(
type DetailsProviderProps = { children: React.ReactNode };
export function DetailsProvider({ children }: DetailsProviderProps) {
const [state, dispatch] = React.useReducer(reducer, {});
-
- const { transactions } = useTransactions();
+ const { transactions, lastFetched } = useTransactions();
const { url } = useCluster();
+ React.useEffect(() => {
+ dispatch({ type: ActionType.Clear });
+ }, [url]);
+
// Filter blocks for current transaction slots
React.useEffect(() => {
- const removeSignatures = new Set();
- const fetchSignatures = new Set();
- transactions.forEach(({ signature, info }) => {
- if (info?.confirmations === "max" && !state[signature])
- fetchSignatures.add(signature);
- else if (info?.confirmations !== "max" && state[signature])
- removeSignatures.add(signature);
- });
-
- const removeList: string[] = [];
- removeSignatures.forEach((s) => removeList.push(s));
- dispatch({ type: ActionType.Remove, signatures: removeList });
-
- const fetchList: string[] = [];
- fetchSignatures.forEach((s) => fetchList.push(s));
- dispatch({ type: ActionType.Add, signatures: fetchList });
- fetchSignatures.forEach((signature) => {
- fetchDetails(dispatch, signature, url);
- });
- }, [transactions]); // eslint-disable-line react-hooks/exhaustive-deps
+ if (lastFetched) {
+ const confirmed =
+ transactions[lastFetched] &&
+ transactions[lastFetched].info?.confirmations === "max";
+ const noDetails = !state[lastFetched];
+ if (confirmed && noDetails) {
+ dispatch({ type: ActionType.Add, signature: lastFetched });
+ fetchDetails(dispatch, lastFetched, url);
+ }
+ }
+ }, [transactions, lastFetched]); // eslint-disable-line react-hooks/exhaustive-deps
return (
diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx
index 9e07e1f8f8..77aa77cae4 100644
--- a/explorer/src/providers/transactions/index.tsx
+++ b/explorer/src/providers/transactions/index.tsx
@@ -8,7 +8,7 @@ import {
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
-import { useQuery } from "../../utils/url";
+import { useQuery } from "utils/url";
import { useCluster, Cluster, ClusterStatus } from "../cluster";
import {
DetailsProvider,
@@ -36,7 +36,6 @@ export interface TransactionStatusInfo {
}
export interface TransactionStatus {
- id: number;
fetchStatus: FetchStatus;
signature: TransactionSignature;
info?: TransactionStatusInfo;
@@ -44,13 +43,14 @@ export interface TransactionStatus {
type Transactions = { [signature: string]: TransactionStatus };
interface State {
- idCounter: number;
transactions: Transactions;
+ lastFetched: TransactionSignature | undefined;
}
export enum ActionType {
UpdateStatus,
FetchSignature,
+ Clear,
}
interface UpdateStatus {
@@ -65,14 +65,18 @@ interface FetchSignature {
signature: TransactionSignature;
}
-type Action = UpdateStatus | FetchSignature;
+interface Clear {
+ type: ActionType.Clear;
+}
+type Action = UpdateStatus | FetchSignature | Clear;
type Dispatch = (action: Action) => void;
function reducer(state: State, action: Action): State {
switch (action.type) {
case ActionType.FetchSignature: {
- const transaction = state.transactions[action.signature];
+ const signature = action.signature;
+ const transaction = state.transactions[signature];
if (transaction) {
const transactions = {
...state.transactions,
@@ -82,18 +86,16 @@ function reducer(state: State, action: Action): State {
info: undefined,
},
};
- return { ...state, transactions };
+ return { ...state, transactions, lastFetched: signature };
} else {
- const nextId = state.idCounter + 1;
const transactions = {
...state.transactions,
[action.signature]: {
- id: nextId,
signature: action.signature,
fetchStatus: FetchStatus.Fetching,
},
};
- return { ...state, transactions, idCounter: nextId };
+ return { ...state, transactions, lastFetched: signature };
}
}
@@ -112,6 +114,13 @@ function reducer(state: State, action: Action): State {
}
break;
}
+
+ case ActionType.Clear: {
+ return {
+ ...state,
+ transactions: {},
+ };
+ }
}
return state;
}
@@ -124,8 +133,8 @@ const DispatchContext = React.createContext(undefined);
type TransactionsProviderProps = { children: React.ReactNode };
export function TransactionsProvider({ children }: TransactionsProviderProps) {
const [state, dispatch] = React.useReducer(reducer, {
- idCounter: 0,
transactions: {},
+ lastFetched: undefined,
});
const { cluster, status: clusterStatus, url } = useCluster();
@@ -135,9 +144,11 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
// Check transaction statuses whenever cluster updates
React.useEffect(() => {
- Object.keys(state.transactions).forEach((signature) => {
- fetchTransactionStatus(dispatch, signature, url, clusterStatus);
- });
+ if (clusterStatus === ClusterStatus.Connecting) {
+ dispatch({ type: ActionType.Clear });
+ } else if (clusterStatus === ClusterStatus.Connected && state.lastFetched) {
+ fetchTransactionStatus(dispatch, state.lastFetched, url);
+ }
// Create a test transaction
if (cluster === Cluster.Devnet && testFlag !== null) {
@@ -145,23 +156,6 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
}
}, [testFlag, cluster, clusterStatus, url]); // eslint-disable-line react-hooks/exhaustive-deps
- // Check for transactions in the url params
- const values = TX_ALIASES.flatMap((key) => [
- query.get(key),
- query.get(key + "s"),
- ]);
- React.useEffect(() => {
- values
- .filter((value): value is string => value !== null)
- .flatMap((value) => value.split(","))
- // Remove duplicates
- .filter((item, pos, self) => self.indexOf(item) === pos)
- .filter((signature) => !state.transactions[signature])
- .forEach((signature) => {
- fetchTransactionStatus(dispatch, signature, url, clusterStatus);
- });
- }, [values.toString()]); // eslint-disable-line react-hooks/exhaustive-deps
-
return (
@@ -189,7 +183,7 @@ async function createTestTransaction(
testAccount.publicKey,
100000
);
- fetchTransactionStatus(dispatch, signature, url, clusterStatus);
+ fetchTransactionStatus(dispatch, signature, url);
fetchAccount(testAccount.publicKey);
} catch (error) {
console.error("Failed to create test success transaction", error);
@@ -208,7 +202,7 @@ async function createTestTransaction(
[testAccount],
{ confirmations: 1, skipPreflight: false }
);
- fetchTransactionStatus(dispatch, signature, url, clusterStatus);
+ fetchTransactionStatus(dispatch, signature, url);
} catch (error) {
console.error("Failed to create test failure transaction", error);
}
@@ -217,17 +211,13 @@ async function createTestTransaction(
export async function fetchTransactionStatus(
dispatch: Dispatch,
signature: TransactionSignature,
- url: string,
- status: ClusterStatus
+ url: string
) {
dispatch({
type: ActionType.FetchSignature,
signature,
});
- // We will auto-refetch when status is no longer connecting
- if (status === ClusterStatus.Connecting) return;
-
let fetchStatus;
let info: TransactionStatusInfo | undefined;
if (isCached(url, signature)) {
@@ -281,12 +271,7 @@ export function useTransactions() {
`useTransactions must be used within a TransactionsProvider`
);
}
- return {
- idCounter: context.idCounter,
- transactions: Object.values(context.transactions).sort((a, b) =>
- a.id <= b.id ? 1 : -1
- ),
- };
+ return context;
}
export function useTransactionStatus(signature: TransactionSignature) {
@@ -321,8 +306,8 @@ export function useFetchTransactionStatus() {
);
}
- const { url, status } = useCluster();
+ const { url } = useCluster();
return (signature: TransactionSignature) => {
- fetchTransactionStatus(dispatch, signature, url, status);
+ fetchTransactionStatus(dispatch, signature, url);
};
}