diff --git a/explorer/src/components/TransactionModal.tsx b/explorer/src/components/TransactionModal.tsx index fedef9f3db..bcc0a85c6d 100644 --- a/explorer/src/components/TransactionModal.tsx +++ b/explorer/src/components/TransactionModal.tsx @@ -3,15 +3,15 @@ import { useDetails, useTransactions, useTransactionsDispatch, - ActionType, - Selected + ActionType } from "../providers/transactions"; import { displayAddress, decodeCreate, decodeTransfer } from "../utils/tx"; import { LAMPORTS_PER_SOL, TransferParams, CreateAccountParams, - TransactionInstruction + TransactionInstruction, + TransactionSignature } from "@solana/web3.js"; import Copyable from "./Copyable"; import Overlay from "./Overlay"; @@ -36,7 +36,7 @@ function TransactionModal() { - + @@ -53,8 +53,12 @@ function TransactionModal() { ); } -function TransactionDetails({ selected }: { selected: Selected }) { - const details = useDetails(selected.signature); +function TransactionDetails({ + signature +}: { + signature: TransactionSignature; +}) { + const details = useDetails(signature); const renderError = (content: React.ReactNode) => { return ( diff --git a/explorer/src/components/TransactionsCard.tsx b/explorer/src/components/TransactionsCard.tsx index 010beca20b..b4ab4705a9 100644 --- a/explorer/src/components/TransactionsCard.tsx +++ b/explorer/src/components/TransactionsCard.tsx @@ -4,8 +4,8 @@ import { useTransactionsDispatch, checkTransactionStatus, ActionType, - Transaction, - Status + TransactionState, + FetchStatus } from "../providers/transactions"; import bs58 from "bs58"; import { assertUnreachable } from "../utils"; @@ -115,50 +115,50 @@ const renderHeader = () => { }; const renderTransactionRow = ( - transaction: Transaction, + transaction: TransactionState, dispatch: any, url: string ) => { + const { fetchStatus, transactionStatus } = transaction; + let statusText; let statusClass; - switch (transaction.status) { - case Status.CheckFailed: + switch (fetchStatus) { + case FetchStatus.FetchFailed: statusClass = "dark"; statusText = "Cluster Error"; break; - case Status.Checking: + case FetchStatus.Fetching: statusClass = "info"; - statusText = "Checking"; + statusText = "Fetching"; break; - case Status.Success: - statusClass = "success"; - statusText = "Success"; - break; - case Status.Failure: - statusClass = "danger"; - statusText = "Failed"; - break; - case Status.Missing: - statusClass = "warning"; - statusText = "Not Found"; + case FetchStatus.Fetched: { + if (!transactionStatus) { + statusClass = "warning"; + statusText = "Not Found"; + } else if (transactionStatus.result.err) { + statusClass = "danger"; + statusText = "Failed"; + } else { + statusClass = "success"; + statusText = "Success"; + } break; + } default: - return assertUnreachable(transaction.status); + return assertUnreachable(fetchStatus); } let slotText = "-"; - if (transaction.slot !== undefined) { - slotText = `${transaction.slot}`; - } - let confirmationsText = "-"; - if (transaction.confirmations !== undefined) { - confirmationsText = `${transaction.confirmations}`; + if (transactionStatus) { + slotText = `${transactionStatus.slot}`; + confirmationsText = `${transactionStatus.confirmations}`; } const renderDetails = () => { let onClick, icon; - if (transaction.confirmations === "max") { + if (transactionStatus?.confirmations === "max") { icon = "more-horizontal"; onClick = () => dispatch({ diff --git a/explorer/src/providers/transactions/details.tsx b/explorer/src/providers/transactions/details.tsx index 574c0b05ee..45a8f4f6a6 100644 --- a/explorer/src/providers/transactions/details.tsx +++ b/explorer/src/providers/transactions/details.tsx @@ -97,9 +97,9 @@ export function DetailsProvider({ children }: DetailsProviderProps) { if (status !== ClusterStatus.Connected) return; const fetchSignatures = new Set(); - transactions.forEach(tx => { - if (tx.slot && tx.confirmations === "max" && !state[tx.signature]) - fetchSignatures.add(tx.signature); + transactions.forEach(({ signature, transactionStatus }) => { + if (transactionStatus?.confirmations === "max" && !state[signature]) + fetchSignatures.add(signature); }); const fetchList: string[] = []; diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx index 71d2294f40..929411eb0f 100644 --- a/explorer/src/providers/transactions/index.tsx +++ b/explorer/src/providers/transactions/index.tsx @@ -3,7 +3,8 @@ import { TransactionSignature, Connection, SystemProgram, - Account + Account, + SignatureResult } from "@solana/web3.js"; import { findGetParameter, findPathSegment } from "../../utils/url"; import { useCluster, ClusterStatus } from "../cluster"; @@ -20,12 +21,10 @@ import { ActionType as AccountsActionType } from "../accounts"; -export enum Status { - Checking, - CheckFailed, - Success, - Failure, - Missing +export enum FetchStatus { + Fetching, + FetchFailed, + Fetched } enum Source { @@ -35,24 +34,24 @@ enum Source { export type Confirmations = number | "max"; -export interface Transaction { - id: number; - status: Status; - source: Source; - slot?: number; - confirmations?: Confirmations; - signature: TransactionSignature; -} - -export interface Selected { +export interface TransactionStatus { slot: number; - signature: TransactionSignature; + result: SignatureResult; + confirmations: Confirmations; } -type Transactions = { [signature: string]: Transaction }; +export interface TransactionState { + id: number; + source: Source; + fetchStatus: FetchStatus; + signature: TransactionSignature; + transactionStatus?: TransactionStatus; +} + +type Transactions = { [signature: string]: TransactionState }; interface State { idCounter: number; - selected?: Selected; + selected?: TransactionSignature; transactions: Transactions; } @@ -75,9 +74,8 @@ interface DeselectTransaction { interface UpdateStatus { type: ActionType.UpdateStatus; signature: TransactionSignature; - status: Status; - slot?: number; - confirmations?: Confirmations; + fetchStatus: FetchStatus; + transactionStatus?: TransactionStatus; } interface InputSignature { @@ -90,6 +88,7 @@ type Action = | InputSignature | SelectTransaction | DeselectTransaction; + type Dispatch = (action: Action) => void; function reducer(state: State, action: Action): State { @@ -99,37 +98,33 @@ function reducer(state: State, action: Action): State { } case ActionType.Select: { const tx = state.transactions[action.signature]; - if (!tx.slot) return state; - const selected = { - slot: tx.slot, - signature: tx.signature - }; - return { ...state, selected }; + return { ...state, selected: tx.signature }; } case ActionType.InputSignature: { if (!!state.transactions[action.signature]) return state; - const idCounter = state.idCounter + 1; + const nextId = state.idCounter + 1; const transactions = { ...state.transactions, [action.signature]: { - id: idCounter, - status: Status.Checking, + id: nextId, source: Source.Input, - signature: action.signature + signature: action.signature, + fetchStatus: FetchStatus.Fetching } }; - return { ...state, transactions, idCounter }; + return { ...state, transactions, idCounter: nextId }; } case ActionType.UpdateStatus: { let transaction = state.transactions[action.signature]; if (transaction) { transaction = { ...transaction, - status: action.status, - slot: action.slot, - confirmations: action.confirmations + fetchStatus: action.fetchStatus }; + if (action.transactionStatus) { + transaction.transactionStatus = action.transactionStatus; + } const transactions = { ...state.transactions, [action.signature]: transaction @@ -171,13 +166,14 @@ function initState(): State { const transactions = signatures.reduce( (transactions: Transactions, signature) => { if (!!transactions[signature]) return transactions; - idCounter++; + const nextId = idCounter + 1; transactions[signature] = { - id: idCounter, + id: nextId, + source: Source.Url, signature, - status: Status.Checking, - source: Source.Url + fetchStatus: FetchStatus.Fetching }; + idCounter++; return transactions; }, {} @@ -269,44 +265,41 @@ export async function checkTransactionStatus( ) { dispatch({ type: ActionType.UpdateStatus, - status: Status.Checking, - signature + signature, + fetchStatus: FetchStatus.Fetching }); - let status; - let slot; - let confirmations: Confirmations | undefined; + let fetchStatus; + let transactionStatus: TransactionStatus | undefined; try { const { value } = await new Connection(url).getSignatureStatus(signature, { searchTransactionHistory: true }); - if (value === null) { - status = Status.Missing; - } else { - slot = value.slot; + if (value !== null) { + let confirmations: Confirmations; if (typeof value.confirmations === "number") { confirmations = value.confirmations; } else { confirmations = "max"; } - if (value.err) { - status = Status.Failure; - } else { - status = Status.Success; - } + transactionStatus = { + slot: value.slot, + confirmations, + result: { err: value.err } + }; } + fetchStatus = FetchStatus.Fetched; } catch (error) { - console.error("Failed to check transaction status", error); - status = Status.CheckFailed; + console.error("Failed to fetch transaction status", error); + fetchStatus = FetchStatus.FetchFailed; } dispatch({ type: ActionType.UpdateStatus, - status, - slot, - confirmations, - signature + signature, + fetchStatus, + transactionStatus }); }