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