Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 101 additions & 28 deletions explorer/src/pages/TransactionDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,24 @@ import { intoTransactionInstruction } from "utils/tx";
import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard";
import { FetchStatus } from "providers/cache";

type Props = { signature: TransactionSignature };
export function TransactionDetailsPage({ signature: raw }: Props) {
const AUTO_REFRESH_INTERVAL = 2000;
const ZERO_CONFIRMATION_BAILOUT = 5;

type SignatureProps = {
signature: TransactionSignature;
};

enum AutoRefresh {
Active,
Inactive,
BailedOut,
}

type AutoRefreshProps = {
autoRefresh: AutoRefresh;
};

export function TransactionDetailsPage({ signature: raw }: SignatureProps) {
let signature: TransactionSignature | undefined;

try {
Expand All @@ -40,6 +56,38 @@ export function TransactionDetailsPage({ signature: raw }: Props) {
}
} catch (err) {}

const status = useTransactionStatus(signature);
const [zeroConfirmationRetries, setZeroConfirmationRetries] = React.useState(
0
);

let autoRefresh = AutoRefresh.Inactive;

if (zeroConfirmationRetries >= ZERO_CONFIRMATION_BAILOUT) {
autoRefresh = AutoRefresh.BailedOut;
} else if (status?.data?.info && status.data.info.confirmations !== "max") {
autoRefresh = AutoRefresh.Active;
}

React.useEffect(() => {
if (
status?.status === FetchStatus.Fetched &&
status.data?.info &&
status.data.info.confirmations === 0
) {
setZeroConfirmationRetries((retries) => retries + 1);
}
}, [status]);

React.useEffect(() => {
if (
status?.status === FetchStatus.Fetching &&
autoRefresh === AutoRefresh.BailedOut
) {
setZeroConfirmationRetries(0);
}
}, [status, autoRefresh, setZeroConfirmationRetries]);

return (
<div className="container mt-n3">
<div className="header">
Expand All @@ -52,28 +100,23 @@ export function TransactionDetailsPage({ signature: raw }: Props) {
<ErrorCard text={`Signature "${raw}" is not valid`} />
) : (
<>
<StatusCard signature={signature} />
<AccountsCard signature={signature} />
<StatusCard signature={signature} autoRefresh={autoRefresh} />
<AccountsCard signature={signature} autoRefresh={autoRefresh} />
<InstructionsSection signature={signature} />
</>
)}
</div>
);
}

function StatusCard({ signature }: Props) {
function StatusCard({
signature,
autoRefresh,
}: SignatureProps & AutoRefreshProps) {
const fetchStatus = useFetchTransactionStatus();
const status = useTransactionStatus(signature);
const fetchDetails = useFetchTransactionDetails();
const details = useTransactionDetails(signature);
const { firstAvailableBlock, status: clusterStatus } = useCluster();
const refresh = React.useCallback(
(signature: string) => {
fetchStatus(signature);
fetchDetails(signature);
},
[fetchStatus, fetchDetails]
);

// Fetch transaction on load
React.useEffect(() => {
Expand All @@ -82,7 +125,25 @@ function StatusCard({ signature }: Props) {
}
}, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps

if (!status || status.status === FetchStatus.Fetching) {
// Effect to set and clear interval for auto-refresh
React.useEffect(() => {
if (autoRefresh === AutoRefresh.Active) {
let intervalHandle: NodeJS.Timeout = setInterval(
() => fetchStatus(signature),
AUTO_REFRESH_INTERVAL
);

return () => {
clearInterval(intervalHandle);
};
}
}, [autoRefresh, fetchStatus, signature]);

if (
!status ||
(status.status === FetchStatus.Fetching &&
autoRefresh === AutoRefresh.Inactive)
) {
return <LoadingCard />;
} else if (status.status === FetchStatus.FetchFailed) {
return (
Expand All @@ -102,6 +163,7 @@ function StatusCard({ signature }: Props) {
}

const { info } = status.data;

const renderResult = () => {
let statusClass = "success";
let statusText = "Success";
Expand Down Expand Up @@ -134,13 +196,17 @@ function StatusCard({ signature }: Props) {
<div className="card">
<div className="card-header align-items-center">
<h3 className="card-header-title">Overview</h3>
<button
className="btn btn-white btn-sm"
onClick={() => refresh(signature)}
>
<span className="fe fe-refresh-cw mr-2"></span>
Refresh
</button>
{autoRefresh === AutoRefresh.Active ? (
<span className="spinner-grow spinner-grow-sm"></span>
) : (
<button
className="btn btn-white btn-sm"
onClick={() => fetchStatus(signature)}
>
<span className="fe fe-refresh-cw mr-2"></span>
Refresh
</button>
)}
</div>

<TableCardBody>
Expand Down Expand Up @@ -211,13 +277,16 @@ function StatusCard({ signature }: Props) {
);
}

function AccountsCard({ signature }: Props) {
function AccountsCard({
signature,
autoRefresh,
}: SignatureProps & AutoRefreshProps) {
const { url } = useCluster();
const details = useTransactionDetails(signature);
const fetchStatus = useFetchTransactionStatus();
const fetchDetails = useFetchTransactionDetails();
const refreshStatus = () => fetchStatus(signature);
const fetchStatus = useFetchTransactionStatus();
const refreshDetails = () => fetchDetails(signature);
const refreshStatus = () => fetchStatus(signature);
const transaction = details?.data?.transaction?.transaction;
const message = transaction?.message;
const status = useTransactionStatus(signature);
Expand All @@ -231,14 +300,18 @@ function AccountsCard({ signature }: Props) {

if (!status?.data?.info) {
return null;
} else if (!details) {
} else if (autoRefresh === AutoRefresh.BailedOut) {
return (
<ErrorCard
retry={refreshStatus}
text="Details are not available until the transaction reaches MAX confirmations"
retry={refreshStatus}
/>
);
} else if (details.status === FetchStatus.Fetching) {
} else if (autoRefresh === AutoRefresh.Active) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think it would be nice to show this error card when auto refresh has been bailed out, thoughts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree! I added that case. If it doesn't make sense to have a retry button I can pull that out.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retry makes sense! But it should be refreshStatus because otherwise the details could get refreshed but the status would be still stuck showing 0 confirmations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, done!

return (
<ErrorCard text="Details are not available until the transaction reaches MAX confirmations" />
);
} else if (!details || details.status === FetchStatus.Fetching) {
return <LoadingCard />;
} else if (details.status === FetchStatus.FetchFailed) {
return <ErrorCard retry={refreshDetails} text="Fetch Failed" />;
Expand Down Expand Up @@ -317,7 +390,7 @@ function AccountsCard({ signature }: Props) {
);
}

function InstructionsSection({ signature }: Props) {
function InstructionsSection({ signature }: SignatureProps) {
const status = useTransactionStatus(signature);
const details = useTransactionDetails(signature);
const fetchDetails = useFetchTransactionDetails();
Expand Down
6 changes: 5 additions & 1 deletion explorer/src/providers/transactions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function useTransactions() {
}

export function useTransactionStatus(
signature: TransactionSignature
signature: TransactionSignature | undefined
): Cache.CacheEntry<TransactionStatus> | undefined {
const context = React.useContext(StateContext);

Expand All @@ -141,6 +141,10 @@ export function useTransactionStatus(
);
}

if (signature === undefined) {
return undefined;
}

return context.entries[signature];
}

Expand Down