Fix false negative not found response
This commit is contained in:
parent
e31b30c595
commit
b68dc5cf85
|
@ -2,12 +2,10 @@ import React from "react";
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
import {
|
import {
|
||||||
Source,
|
Source,
|
||||||
|
useFetchTransactionStatus,
|
||||||
useTransactionStatus,
|
useTransactionStatus,
|
||||||
useTransactionDetails,
|
useTransactionDetails,
|
||||||
useTransactionsDispatch,
|
|
||||||
useDetailsDispatch,
|
useDetailsDispatch,
|
||||||
checkTransactionStatus,
|
|
||||||
ActionType,
|
|
||||||
FetchStatus
|
FetchStatus
|
||||||
} from "../providers/transactions";
|
} from "../providers/transactions";
|
||||||
import { fetchDetails } from "providers/transactions/details";
|
import { fetchDetails } from "providers/transactions/details";
|
||||||
|
@ -28,8 +26,7 @@ import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
type Props = { signature: TransactionSignature };
|
type Props = { signature: TransactionSignature };
|
||||||
export default function TransactionDetails({ signature }: Props) {
|
export default function TransactionDetails({ signature }: Props) {
|
||||||
const dispatch = useTransactionsDispatch();
|
const fetchTransaction = useFetchTransactionStatus();
|
||||||
const { url } = useCluster();
|
|
||||||
const [, setShow] = useClusterModal();
|
const [, setShow] = useClusterModal();
|
||||||
const [search, setSearch] = React.useState(signature);
|
const [search, setSearch] = React.useState(signature);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -41,13 +38,8 @@ export default function TransactionDetails({ signature }: Props) {
|
||||||
|
|
||||||
// Fetch transaction on load
|
// Fetch transaction on load
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
dispatch({
|
fetchTransaction(signature, Source.Url);
|
||||||
type: ActionType.FetchSignature,
|
}, [signature]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
signature,
|
|
||||||
source: Source.Url
|
|
||||||
});
|
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
|
||||||
}, [signature, dispatch, url]);
|
|
||||||
|
|
||||||
const searchInput = (
|
const searchInput = (
|
||||||
<input
|
<input
|
||||||
|
@ -104,20 +96,15 @@ export default function TransactionDetails({ signature }: Props) {
|
||||||
|
|
||||||
function StatusCard({ signature }: Props) {
|
function StatusCard({ signature }: Props) {
|
||||||
const status = useTransactionStatus(signature);
|
const status = useTransactionStatus(signature);
|
||||||
const dispatch = useTransactionsDispatch();
|
const refresh = useFetchTransactionStatus();
|
||||||
const details = useTransactionDetails(signature);
|
const details = useTransactionDetails(signature);
|
||||||
const { url } = useCluster();
|
|
||||||
|
|
||||||
const refreshStatus = () => {
|
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
|
||||||
};
|
|
||||||
|
|
||||||
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) {
|
||||||
return <RetryCard retry={refreshStatus} text="Fetch Failed" />;
|
return <RetryCard retry={() => refresh(signature)} text="Fetch Failed" />;
|
||||||
} else if (!status.info) {
|
} else if (!status.info) {
|
||||||
return <RetryCard retry={refreshStatus} text="Not Found" />;
|
return <RetryCard retry={() => refresh(signature)} text="Not Found" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { info } = status;
|
const { info } = status;
|
||||||
|
@ -141,7 +128,10 @@ function StatusCard({ signature }: Props) {
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-header align-items-center">
|
<div className="card-header align-items-center">
|
||||||
<h3 className="card-header-title">Status</h3>
|
<h3 className="card-header-title">Status</h3>
|
||||||
<button className="btn btn-white btn-sm" onClick={refreshStatus}>
|
<button
|
||||||
|
className="btn btn-white btn-sm"
|
||||||
|
onClick={() => refresh(signature)}
|
||||||
|
>
|
||||||
<span className="fe fe-refresh-cw mr-2"></span>
|
<span className="fe fe-refresh-cw mr-2"></span>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
|
@ -179,14 +169,25 @@ function AccountsCard({ signature }: Props) {
|
||||||
const dispatch = useDetailsDispatch();
|
const dispatch = useDetailsDispatch();
|
||||||
const { url } = useCluster();
|
const { url } = useCluster();
|
||||||
|
|
||||||
|
const fetchStatus = useFetchTransactionStatus();
|
||||||
|
const refreshStatus = () => fetchStatus(signature);
|
||||||
const refreshDetails = () => fetchDetails(dispatch, signature, url);
|
const refreshDetails = () => fetchDetails(dispatch, signature, url);
|
||||||
const transaction = details?.transaction?.transaction;
|
const transaction = details?.transaction?.transaction;
|
||||||
const message = React.useMemo(() => {
|
const message = React.useMemo(() => {
|
||||||
return transaction?.compileMessage();
|
return transaction?.compileMessage();
|
||||||
}, [transaction]);
|
}, [transaction]);
|
||||||
|
|
||||||
if (!details) {
|
const status = useTransactionStatus(signature);
|
||||||
|
|
||||||
|
if (!status || !status.info) {
|
||||||
return null;
|
return null;
|
||||||
|
} else if (!details) {
|
||||||
|
return (
|
||||||
|
<RetryCard
|
||||||
|
retry={refreshStatus}
|
||||||
|
text="Details are not available until the transaction reaches MAX confirmations"
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (details.fetchStatus === FetchStatus.Fetching) {
|
} else if (details.fetchStatus === FetchStatus.Fetching) {
|
||||||
return <LoadingCard />;
|
return <LoadingCard />;
|
||||||
} else if (details?.fetchStatus === FetchStatus.FetchFailed) {
|
} else if (details?.fetchStatus === FetchStatus.FetchFailed) {
|
||||||
|
@ -571,11 +572,17 @@ function LoadingCard() {
|
||||||
function RetryCard({ retry, text }: { retry: () => void; text: string }) {
|
function RetryCard({ retry, text }: { retry: () => void; text: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body">
|
<div className="card-body text-center">
|
||||||
{text}
|
{text}
|
||||||
<span className="btn btn-white ml-3" onClick={retry}>
|
<span className="btn btn-white ml-3 d-none d-md-inline" onClick={retry}>
|
||||||
Try Again
|
Try Again
|
||||||
</span>
|
</span>
|
||||||
|
<div className="d-block d-md-none mt-4">
|
||||||
|
<hr></hr>
|
||||||
|
<span className="btn btn-white" onClick={retry}>
|
||||||
|
Try Again
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,24 +2,20 @@ import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
useTransactions,
|
useTransactions,
|
||||||
useTransactionsDispatch,
|
|
||||||
checkTransactionStatus,
|
|
||||||
ActionType,
|
|
||||||
TransactionStatus,
|
TransactionStatus,
|
||||||
Source,
|
Source,
|
||||||
FetchStatus
|
FetchStatus,
|
||||||
|
useFetchTransactionStatus
|
||||||
} from "../providers/transactions";
|
} from "../providers/transactions";
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
import { assertUnreachable } from "../utils";
|
import { assertUnreachable } from "../utils";
|
||||||
import { useCluster } from "../providers/cluster";
|
|
||||||
import Copyable from "./Copyable";
|
import Copyable from "./Copyable";
|
||||||
|
|
||||||
function TransactionsCard() {
|
function TransactionsCard() {
|
||||||
const { transactions, idCounter } = useTransactions();
|
const { transactions, idCounter } = useTransactions();
|
||||||
const dispatch = useTransactionsDispatch();
|
const fetchTransaction = useFetchTransactionStatus();
|
||||||
const signatureInput = React.useRef<HTMLInputElement>(null);
|
const signatureInput = React.useRef<HTMLInputElement>(null);
|
||||||
const [error, setError] = React.useState("");
|
const [error, setError] = React.useState("");
|
||||||
const { url } = useCluster();
|
|
||||||
|
|
||||||
const onNew = (signature: string) => {
|
const onNew = (signature: string) => {
|
||||||
if (signature.length === 0) return;
|
if (signature.length === 0) return;
|
||||||
|
@ -37,13 +33,7 @@ function TransactionsCard() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
fetchTransaction(signature, Source.Input);
|
||||||
type: ActionType.FetchSignature,
|
|
||||||
signature,
|
|
||||||
source: Source.Input
|
|
||||||
});
|
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
|
||||||
|
|
||||||
const inputEl = signatureInput.current;
|
const inputEl = signatureInput.current;
|
||||||
if (inputEl) {
|
if (inputEl) {
|
||||||
inputEl.value = "";
|
inputEl.value = "";
|
||||||
|
@ -98,9 +88,7 @@ function TransactionsCard() {
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
{transactions.map(transaction =>
|
{transactions.map(transaction => renderTransactionRow(transaction))}
|
||||||
renderTransactionRow(transaction, dispatch, url)
|
|
||||||
)}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,11 +108,7 @@ const renderHeader = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTransactionRow = (
|
const renderTransactionRow = (transactionStatus: TransactionStatus) => {
|
||||||
transactionStatus: TransactionStatus,
|
|
||||||
dispatch: any,
|
|
||||||
url: string
|
|
||||||
) => {
|
|
||||||
const { fetchStatus, info, signature, id } = transactionStatus;
|
const { fetchStatus, info, signature, id } = transactionStatus;
|
||||||
|
|
||||||
let statusText;
|
let statusText;
|
||||||
|
@ -162,30 +146,6 @@ const renderTransactionRow = (
|
||||||
confirmationsText = `${info.confirmations}`;
|
confirmationsText = `${info.confirmations}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderDetails = () => {
|
|
||||||
if (info?.confirmations === "max") {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={location => ({ ...location, pathname: "/tx/" + signature })}
|
|
||||||
className="btn btn-rounded-circle btn-white btn-sm"
|
|
||||||
>
|
|
||||||
<span className="fe fe-arrow-right"></span>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="btn btn-rounded-circle btn-white btn-sm"
|
|
||||||
onClick={() => {
|
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="fe fe-refresh-cw"></span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={signature}>
|
<tr key={signature}>
|
||||||
<td>
|
<td>
|
||||||
|
@ -201,7 +161,14 @@ const renderTransactionRow = (
|
||||||
</td>
|
</td>
|
||||||
<td className="text-uppercase">{confirmationsText}</td>
|
<td className="text-uppercase">{confirmationsText}</td>
|
||||||
<td>{slotText}</td>
|
<td>{slotText}</td>
|
||||||
<td>{renderDetails()}</td>
|
<td>
|
||||||
|
<Link
|
||||||
|
to={location => ({ ...location, pathname: "/tx/" + signature })}
|
||||||
|
className="btn btn-rounded-circle btn-white btn-sm"
|
||||||
|
>
|
||||||
|
<span className="fe fe-arrow-right"></span>
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,28 +61,24 @@ interface State {
|
||||||
status: ClusterStatus;
|
status: ClusterStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Connecting {
|
interface Action {
|
||||||
status: ClusterStatus.Connecting;
|
status: ClusterStatus;
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
customUrl: string;
|
customUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Connected {
|
|
||||||
status: ClusterStatus.Connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Failure {
|
|
||||||
status: ClusterStatus.Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Action = Connected | Connecting | Failure;
|
|
||||||
type Dispatch = (action: Action) => void;
|
type Dispatch = (action: Action) => void;
|
||||||
|
|
||||||
function clusterReducer(state: State, action: Action): State {
|
function clusterReducer(state: State, action: Action): State {
|
||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ClusterStatus.Connected:
|
case ClusterStatus.Connected:
|
||||||
case ClusterStatus.Failure: {
|
case ClusterStatus.Failure: {
|
||||||
return Object.assign({}, state, { status: action.status });
|
if (
|
||||||
|
state.cluster !== action.cluster ||
|
||||||
|
state.customUrl !== action.customUrl
|
||||||
|
)
|
||||||
|
return state;
|
||||||
|
return action;
|
||||||
}
|
}
|
||||||
case ClusterStatus.Connecting: {
|
case ClusterStatus.Connecting: {
|
||||||
return action;
|
return action;
|
||||||
|
@ -197,10 +193,10 @@ async function updateCluster(
|
||||||
try {
|
try {
|
||||||
const connection = new Connection(clusterUrl(cluster, customUrl));
|
const connection = new Connection(clusterUrl(cluster, customUrl));
|
||||||
await connection.getRecentBlockhash();
|
await connection.getRecentBlockhash();
|
||||||
dispatch({ status: ClusterStatus.Connected });
|
dispatch({ status: ClusterStatus.Connected, cluster, customUrl });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update cluster", error);
|
console.error("Failed to update cluster", error);
|
||||||
dispatch({ status: ClusterStatus.Failure });
|
dispatch({ status: ClusterStatus.Failure, cluster, customUrl });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
SignatureResult
|
SignatureResult
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { useQuery } from "../../utils/url";
|
import { useQuery } from "../../utils/url";
|
||||||
import { useCluster, Cluster } from "../cluster";
|
import { useCluster, Cluster, ClusterStatus } from "../cluster";
|
||||||
import {
|
import {
|
||||||
DetailsProvider,
|
DetailsProvider,
|
||||||
StateContext as DetailsStateContext,
|
StateContext as DetailsStateContext,
|
||||||
|
@ -127,7 +127,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||||
transactions: {}
|
transactions: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cluster, url } = useCluster();
|
const { cluster, status: clusterStatus, url } = useCluster();
|
||||||
const accountsDispatch = useAccountsDispatch();
|
const accountsDispatch = useAccountsDispatch();
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const testFlag = query.get("test");
|
const testFlag = query.get("test");
|
||||||
|
@ -140,14 +140,14 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||||
signature,
|
signature,
|
||||||
source: Source.Url
|
source: Source.Url
|
||||||
});
|
});
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a test transaction
|
// Create a test transaction
|
||||||
if (cluster === Cluster.Devnet && testFlag !== null) {
|
if (cluster === Cluster.Devnet && testFlag !== null) {
|
||||||
createTestTransaction(dispatch, accountsDispatch, url);
|
createTestTransaction(dispatch, accountsDispatch, url, clusterStatus);
|
||||||
}
|
}
|
||||||
}, [testFlag, cluster, 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
|
// Check for transactions in the url params
|
||||||
const values = TX_ALIASES.flatMap(key => [
|
const values = TX_ALIASES.flatMap(key => [
|
||||||
|
@ -167,7 +167,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||||
signature,
|
signature,
|
||||||
source: Source.Url
|
source: Source.Url
|
||||||
});
|
});
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
|
||||||
});
|
});
|
||||||
}, [values.toString()]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [values.toString()]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
@ -183,7 +183,8 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||||
async function createTestTransaction(
|
async function createTestTransaction(
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
accountsDispatch: AccountsDispatch,
|
accountsDispatch: AccountsDispatch,
|
||||||
url: string
|
url: string,
|
||||||
|
clusterStatus: ClusterStatus
|
||||||
) {
|
) {
|
||||||
const testKey = process.env.REACT_APP_TEST_KEY;
|
const testKey = process.env.REACT_APP_TEST_KEY;
|
||||||
let testAccount = new Account();
|
let testAccount = new Account();
|
||||||
|
@ -203,7 +204,7 @@ async function createTestTransaction(
|
||||||
signature,
|
signature,
|
||||||
source: Source.Test
|
source: Source.Test
|
||||||
});
|
});
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
|
||||||
accountsDispatch({
|
accountsDispatch({
|
||||||
type: AccountsActionType.Input,
|
type: AccountsActionType.Input,
|
||||||
pubkey: testAccount.publicKey
|
pubkey: testAccount.publicKey
|
||||||
|
@ -226,16 +227,17 @@ async function createTestTransaction(
|
||||||
signature,
|
signature,
|
||||||
source: Source.Test
|
source: Source.Test
|
||||||
});
|
});
|
||||||
checkTransactionStatus(dispatch, signature, url);
|
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create test failure transaction", error);
|
console.error("Failed to create test failure transaction", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkTransactionStatus(
|
export async function fetchTransactionStatus(
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
signature: TransactionSignature,
|
signature: TransactionSignature,
|
||||||
url: string
|
url: string,
|
||||||
|
status: ClusterStatus
|
||||||
) {
|
) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionType.UpdateStatus,
|
type: ActionType.UpdateStatus,
|
||||||
|
@ -243,6 +245,9 @@ export async function checkTransactionStatus(
|
||||||
fetchStatus: FetchStatus.Fetching
|
fetchStatus: FetchStatus.Fetching
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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;
|
||||||
try {
|
try {
|
||||||
|
@ -317,16 +322,6 @@ export function useTransactionDetails(signature: TransactionSignature) {
|
||||||
return context[signature];
|
return context[signature];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTransactionsDispatch() {
|
|
||||||
const context = React.useContext(DispatchContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error(
|
|
||||||
`useTransactionsDispatch must be used within a TransactionsProvider`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDetailsDispatch() {
|
export function useDetailsDispatch() {
|
||||||
const context = React.useContext(DetailsDispatchContext);
|
const context = React.useContext(DetailsDispatchContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
@ -336,3 +331,25 @@ export function useDetailsDispatch() {
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useFetchTransactionStatus() {
|
||||||
|
const dispatch = React.useContext(DispatchContext);
|
||||||
|
if (!dispatch) {
|
||||||
|
throw new Error(
|
||||||
|
`useFetchTransactionStatus must be used within a TransactionsProvider`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url, status } = useCluster();
|
||||||
|
return (signature: TransactionSignature, source?: Source) => {
|
||||||
|
if (source !== undefined) {
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.FetchSignature,
|
||||||
|
signature,
|
||||||
|
source
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTransactionStatus(dispatch, signature, url, status);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue