Clean up explorer data providers (#11334)

This commit is contained in:
Justin Starry
2020-08-02 20:18:28 +08:00
committed by GitHub
parent 0d8f3139ae
commit b6ea9f1861
6 changed files with 134 additions and 207 deletions

View File

@ -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 (

View File

@ -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) {

View File

@ -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); fetchAccountHistory(
}); dispatch,
new PublicKey(lastFetchedAddress),
const removeList: string[] = []; manager.current,
removeAddresses.forEach((address) => { true
manager.current.removeAccountHistory(address); );
removeList.push(address); }
}); }
dispatch({ type: ActionType.Remove, addresses: removeList }); }, [accounts, lastFetchedAddress]); // eslint-disable-line react-hooks/exhaustive-deps
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
return ( return (
<ManagerContext.Provider value={manager.current}> <ManagerContext.Provider value={manager.current}>

View File

@ -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,40 +120,20 @@ 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) {
fetchAccountInfo(dispatch, new PublicKey(state.lastFetchedAddress), url);
}
}, [status, url]); // eslint-disable-line react-hooks/exhaustive-deps }, [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 ( return (
<StateContext.Provider value={state}> <StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}> <DispatchContext.Provider value={dispatch}>
@ -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);
}; };
} }

View File

@ -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}>

View File

@ -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);
}; };
} }