Squash pending tx initial implementation

This commit is contained in:
Willian Mitsuda 2021-09-04 03:19:42 -03:00
parent 6ac1528d36
commit 50f4abf23f
8 changed files with 207 additions and 139 deletions

View File

@ -2,6 +2,7 @@ import React, { useMemo, useContext } from "react";
import { Route, Switch, useParams } from "react-router-dom"; import { Route, Switch, useParams } from "react-router-dom";
import StandardFrame from "./StandardFrame"; import StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle"; import StandardSubtitle from "./StandardSubtitle";
import ContentFrame from "./ContentFrame";
import Tab from "./components/Tab"; import Tab from "./components/Tab";
import Details from "./transaction/Details"; import Details from "./transaction/Details";
import Logs from "./transaction/Logs"; import Logs from "./transaction/Logs";
@ -28,7 +29,7 @@ const Transaction: React.FC = () => {
} }
for (const t of internalOps) { for (const t of internalOps) {
if (t.to === txData.miner) { if (t.to === txData.confirmedData?.miner) {
return true; return true;
} }
} }
@ -37,18 +38,30 @@ const Transaction: React.FC = () => {
const selectionCtx = useSelection(); const selectionCtx = useSelection();
const blockETHUSDPrice = useETHUSDOracle(provider, txData?.blockNumber); const blockETHUSDPrice = useETHUSDOracle(
provider,
txData?.confirmedData?.blockNumber
);
return ( return (
<StandardFrame> <StandardFrame>
<StandardSubtitle>Transaction Details</StandardSubtitle> <StandardSubtitle>Transaction Details</StandardSubtitle>
{txData === null && (
<ContentFrame>
<div className="py-4 text-sm">
Transaction <span className="font-hash">{txhash}</span> not found.
</div>
</ContentFrame>
)}
{txData && ( {txData && (
<SelectionContext.Provider value={selectionCtx}> <SelectionContext.Provider value={selectionCtx}>
<div className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white"> <div className="flex space-x-2 border-l border-r border-t rounded-t-lg bg-white">
<Tab href={`/tx/${txhash}`}>Overview</Tab> <Tab href={`/tx/${txhash}`}>Overview</Tab>
{txData.confirmedData?.blockNumber !== undefined && (
<Tab href={`/tx/${txhash}/logs`}> <Tab href={`/tx/${txhash}/logs`}>
Logs{txData && ` (${txData.logs.length})`} Logs{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
</Tab> </Tab>
)}
</div> </div>
<Switch> <Switch>
<Route path="/tx/:txhash/" exact> <Route path="/tx/:txhash/" exact>

View File

@ -22,7 +22,9 @@ const InternalSelfDestruct: React.FC<InternalSelfDestructProps> = ({
const { provider } = useContext(RuntimeContext); const { provider } = useContext(RuntimeContext);
const network = provider?.network; const network = provider?.network;
const toMiner = txData.miner !== undefined && internalOp.to === txData.miner; const toMiner =
txData.confirmedData?.miner !== undefined &&
internalOp.to === txData.confirmedData.miner;
return ( return (
<> <>

View File

@ -16,8 +16,11 @@ const InternalTransfer: React.FC<InternalTransferProps> = ({
internalOp, internalOp,
}) => { }) => {
const fromMiner = const fromMiner =
txData.miner !== undefined && internalOp.from === txData.miner; txData.confirmedData?.miner !== undefined &&
const toMiner = txData.miner !== undefined && internalOp.to === txData.miner; internalOp.from === txData.confirmedData.miner;
const toMiner =
txData.confirmedData?.miner !== undefined &&
internalOp.to === txData.confirmedData.miner;
return ( return (
<div className="flex items-baseline space-x-1 text-xs"> <div className="flex items-baseline space-x-1 text-xs">

View File

@ -43,8 +43,8 @@ const Details: React.FC<DetailsProps> = ({
ethUSDPrice, ethUSDPrice,
}) => { }) => {
const hasEIP1559 = const hasEIP1559 =
txData.blockBaseFeePerGas !== undefined && txData.confirmedData?.blockBaseFeePerGas !== undefined &&
txData.blockBaseFeePerGas !== null; txData.confirmedData?.blockBaseFeePerGas !== null;
const [inputMode, setInputMode] = useState<number>(0); const [inputMode, setInputMode] = useState<number>(0);
const utfInput = useMemo(() => { const utfInput = useMemo(() => {
@ -66,7 +66,9 @@ const Details: React.FC<DetailsProps> = ({
</div> </div>
</InfoRow> </InfoRow>
<InfoRow title="Status"> <InfoRow title="Status">
{txData.status ? ( {txData.confirmedData === undefined ? (
<span className="italic text-gray-400">Pending</span>
) : txData.confirmedData.status ? (
<span className="flex items-center w-min rounded-lg space-x-1 px-3 py-1 bg-green-50 text-green-500 text-xs"> <span className="flex items-center w-min rounded-lg space-x-1 px-3 py-1 bg-green-50 text-green-500 text-xs">
<FontAwesomeIcon icon={faCheckCircle} size="1x" /> <FontAwesomeIcon icon={faCheckCircle} size="1x" />
<span>Success</span> <span>Success</span>
@ -78,38 +80,45 @@ const Details: React.FC<DetailsProps> = ({
</span> </span>
)} )}
</InfoRow> </InfoRow>
{txData.confirmedData && (
<>
<InfoRow title="Block / Position"> <InfoRow title="Block / Position">
<div className="flex items-baseline divide-x-2 divide-dotted divide-gray-300"> <div className="flex items-baseline divide-x-2 divide-dotted divide-gray-300">
<div className="flex space-x-1 items-baseline mr-3"> <div className="flex space-x-1 items-baseline mr-3">
<span className="text-orange-500"> <span className="text-orange-500">
<FontAwesomeIcon icon={faCube} /> <FontAwesomeIcon icon={faCube} />
</span> </span>
<BlockLink blockTag={txData.blockNumber} /> <BlockLink blockTag={txData.confirmedData.blockNumber} />
<BlockConfirmations confirmations={txData.confirmations} /> <BlockConfirmations
confirmations={txData.confirmedData.confirmations}
/>
</div> </div>
<div className="flex space-x-2 items-baseline pl-3"> <div className="flex space-x-2 items-baseline pl-3">
<RelativePosition <RelativePosition
pos={txData.transactionIndex} pos={txData.confirmedData.transactionIndex}
total={txData.blockTransactionCount - 1} total={txData.confirmedData.blockTransactionCount - 1}
/> />
<PercentagePosition <PercentagePosition
perc={ perc={
txData.transactionIndex / (txData.blockTransactionCount - 1) txData.confirmedData.transactionIndex /
(txData.confirmedData.blockTransactionCount - 1)
} }
/> />
</div> </div>
</div> </div>
</InfoRow> </InfoRow>
<InfoRow title="Timestamp"> <InfoRow title="Timestamp">
<Timestamp value={txData.timestamp} /> <Timestamp value={txData.confirmedData.timestamp} />
</InfoRow> </InfoRow>
</>
)}
<InfoRow title="From / Nonce"> <InfoRow title="From / Nonce">
<div className="flex divide-x-2 divide-dotted divide-gray-300"> <div className="flex divide-x-2 divide-dotted divide-gray-300">
<div className="flex items-baseline space-x-2 -ml-1 mr-3"> <div className="flex items-baseline space-x-2 -ml-1 mr-3">
<AddressHighlighter address={txData.from}> <AddressHighlighter address={txData.from}>
<DecoratedAddressLink <DecoratedAddressLink
address={txData.from} address={txData.from}
miner={txData.from === txData.miner} miner={txData.from === txData.confirmedData?.miner}
txFrom txFrom
/> />
</AddressHighlighter> </AddressHighlighter>
@ -126,22 +135,28 @@ const Details: React.FC<DetailsProps> = ({
<AddressHighlighter address={txData.to}> <AddressHighlighter address={txData.to}>
<DecoratedAddressLink <DecoratedAddressLink
address={txData.to} address={txData.to}
miner={txData.to === txData.miner} miner={txData.to === txData.confirmedData?.miner}
txTo txTo
/> />
</AddressHighlighter> </AddressHighlighter>
<Copy value={txData.to} /> <Copy value={txData.to} />
</div> </div>
) : txData.confirmedData === undefined ? (
<span className="italic text-gray-400">
Pending contract creation
</span>
) : ( ) : (
<div className="flex items-baseline space-x-2 -ml-1"> <div className="flex items-baseline space-x-2 -ml-1">
<AddressHighlighter address={txData.createdContractAddress!}> <AddressHighlighter
address={txData.confirmedData?.createdContractAddress!}
>
<DecoratedAddressLink <DecoratedAddressLink
address={txData.createdContractAddress!} address={txData.confirmedData.createdContractAddress!}
creation creation
txTo txTo
/> />
</AddressHighlighter> </AddressHighlighter>
<Copy value={txData.createdContractAddress!} /> <Copy value={txData.confirmedData.createdContractAddress!} />
</div> </div>
)} )}
{internalOps && internalOps.length > 0 && ( {internalOps && internalOps.length > 0 && (
@ -218,6 +233,7 @@ const Details: React.FC<DetailsProps> = ({
</InfoRow> </InfoRow>
</> </>
)} )}
{txData.gasPrice && (
<InfoRow title="Gas Price"> <InfoRow title="Gas Price">
<div className="flex items-baseline space-x-1"> <div className="flex items-baseline space-x-1">
<span> <span>
@ -231,44 +247,54 @@ const Details: React.FC<DetailsProps> = ({
)} )}
</div> </div>
</InfoRow> </InfoRow>
)}
{txData.confirmedData && (
<InfoRow title="Gas Used / Limit"> <InfoRow title="Gas Used / Limit">
<div className="flex space-x-3 items-baseline"> <div className="flex space-x-3 items-baseline">
<div> <div>
<RelativePosition <RelativePosition
pos={<GasValue value={txData.gasUsed} />} pos={<GasValue value={txData.confirmedData.gasUsed} />}
total={<GasValue value={txData.gasLimit} />} total={<GasValue value={txData.gasLimit} />}
/> />
</div> </div>
<PercentageBar <PercentageBar
perc={ perc={
Math.round( Math.round(
(txData.gasUsed.toNumber() / txData.gasLimit.toNumber()) * 10000 (txData.confirmedData.gasUsed.toNumber() /
txData.gasLimit.toNumber()) *
10000
) / 100 ) / 100
} }
/> />
</div> </div>
</InfoRow> </InfoRow>
{hasEIP1559 && ( )}
{txData.confirmedData && hasEIP1559 && (
<InfoRow title="Block Base Fee"> <InfoRow title="Block Base Fee">
<span> <span>
<FormattedBalance value={txData.blockBaseFeePerGas!} decimals={9} />{" "} <FormattedBalance
value={txData.confirmedData.blockBaseFeePerGas!}
decimals={9}
/>{" "}
Gwei ( Gwei (
<FormattedBalance <FormattedBalance
value={txData.blockBaseFeePerGas!} value={txData.confirmedData.blockBaseFeePerGas!}
decimals={0} decimals={0}
/>{" "} />{" "}
wei) wei)
</span> </span>
</InfoRow> </InfoRow>
)} )}
{txData.confirmedData && (
<>
<InfoRow title="Transaction Fee"> <InfoRow title="Transaction Fee">
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<FormattedBalance value={txData.fee} /> Ether{" "} <FormattedBalance value={txData.confirmedData.fee} /> Ether{" "}
{ethUSDPrice && ( {ethUSDPrice && (
<span className="px-2 border-skin-from border rounded-lg bg-skin-from text-skin-from"> <span className="px-2 border-skin-from border rounded-lg bg-skin-from text-skin-from">
<ETH2USDValue <ETH2USDValue
ethAmount={txData.fee} ethAmount={txData.confirmedData.fee}
eth2USDValue={ethUSDPrice} eth2USDValue={ethUSDPrice}
/> />
</span> </span>
@ -280,6 +306,8 @@ const Details: React.FC<DetailsProps> = ({
<InfoRow title="Ether Price"> <InfoRow title="Ether Price">
<USDValue value={ethUSDPrice} /> <USDValue value={ethUSDPrice} />
</InfoRow> </InfoRow>
</>
)}
<InfoRow title="Input Data"> <InfoRow title="Input Data">
<div className="space-y-1"> <div className="space-y-1">
<div className="flex space-x-1"> <div className="flex space-x-1">

View File

@ -10,8 +10,8 @@ type LogsProps = {
const Logs: React.FC<LogsProps> = ({ txData }) => ( const Logs: React.FC<LogsProps> = ({ txData }) => (
<ContentFrame tabs> <ContentFrame tabs>
<div className="text-sm py-4">Transaction Receipt Event Logs</div> <div className="text-sm py-4">Transaction Receipt Event Logs</div>
{txData && {txData.confirmedData &&
txData.logs.map((l, i) => ( txData.confirmedData.logs.map((l, i) => (
<div className="flex space-x-10 py-5" key={i}> <div className="flex space-x-10 py-5" key={i}>
<div> <div>
<span className="rounded-full w-12 h-12 flex items-center justify-center bg-green-50 text-green-500"> <span className="rounded-full w-12 h-12 flex items-center justify-center bg-green-50 text-green-500">
@ -24,7 +24,7 @@ const Logs: React.FC<LogsProps> = ({ txData }) => (
<div className="col-span-11 mr-auto"> <div className="col-span-11 mr-auto">
<DecoratedAddressLink <DecoratedAddressLink
address={l.address} address={l.address}
miner={l.address === txData.miner} miner={l.address === txData.confirmedData?.miner}
txFrom={l.address === txData.from} txFrom={l.address === txData.from}
txTo={l.address === txData.to} txTo={l.address === txData.to}
/> />

View File

@ -11,8 +11,10 @@ type RewardSplitProps = {
}; };
const RewardSplit: React.FC<RewardSplitProps> = ({ txData }) => { const RewardSplit: React.FC<RewardSplitProps> = ({ txData }) => {
const paidFees = txData.gasPrice.mul(txData.gasUsed); const paidFees = txData.gasPrice.mul(txData.confirmedData!.gasUsed);
const burntFees = txData.blockBaseFeePerGas!.mul(txData.gasUsed); const burntFees = txData.confirmedData!.blockBaseFeePerGas!.mul(
txData.confirmedData!.gasUsed
);
const minerReward = paidFees.sub(burntFees); const minerReward = paidFees.sub(burntFees);
const burntPerc = const burntPerc =

View File

@ -38,29 +38,33 @@ export type ENSReverseCache = {
export type TransactionData = { export type TransactionData = {
transactionHash: string; transactionHash: string;
status: boolean;
blockNumber: number;
transactionIndex: number;
blockTransactionCount: number;
confirmations: number;
timestamp: number;
miner?: string;
from: string; from: string;
to: string; to?: string;
createdContractAddress?: string;
value: BigNumber; value: BigNumber;
tokenTransfers: TokenTransfer[]; tokenTransfers: TokenTransfer[];
tokenMetas: TokenMetas; tokenMetas: TokenMetas;
type: number; type: number;
maxFeePerGas?: BigNumber | undefined; maxFeePerGas?: BigNumber | undefined;
maxPriorityFeePerGas?: BigNumber | undefined; maxPriorityFeePerGas?: BigNumber | undefined;
fee: BigNumber;
blockBaseFeePerGas?: BigNumber | undefined | null;
gasPrice: BigNumber; gasPrice: BigNumber;
gasUsed: BigNumber;
gasLimit: BigNumber; gasLimit: BigNumber;
nonce: number; nonce: number;
data: string; data: string;
confirmedData?: ConfirmedTransactionData | undefined;
};
export type ConfirmedTransactionData = {
status: boolean;
blockNumber: number;
transactionIndex: number;
blockBaseFeePerGas?: BigNumber | undefined | null;
blockTransactionCount: number;
confirmations: number;
timestamp: number;
miner: string;
createdContractAddress?: string;
fee: BigNumber;
gasUsed: BigNumber;
logs: Log[]; logs: Log[];
}; };

View File

@ -174,8 +174,8 @@ export const useBlockData = (
export const useTxData = ( export const useTxData = (
provider: JsonRpcProvider | undefined, provider: JsonRpcProvider | undefined,
txhash: string txhash: string
): TransactionData | undefined => { ): TransactionData | undefined | null => {
const [txData, setTxData] = useState<TransactionData>(); const [txData, setTxData] = useState<TransactionData | undefined | null>();
useEffect(() => { useEffect(() => {
if (!provider) { if (!provider) {
@ -187,11 +187,21 @@ export const useTxData = (
provider.getTransaction(txhash), provider.getTransaction(txhash),
provider.getTransactionReceipt(txhash), provider.getTransactionReceipt(txhash),
]); ]);
const _block = await readBlock(provider, _receipt.blockNumber.toString()); if (_response === null) {
setTxData(null);
return;
}
let _block: ExtendedBlock | undefined;
if (_response.blockNumber) {
_block = await readBlock(provider, _response.blockNumber.toString());
}
document.title = `Transaction ${_response.hash} | Otterscan`; document.title = `Transaction ${_response.hash} | Otterscan`;
// Extract token transfers // Extract token transfers
const tokenTransfers: TokenTransfer[] = []; const tokenTransfers: TokenTransfer[] = [];
if (_receipt) {
for (const l of _receipt.logs) { for (const l of _receipt.logs) {
if (l.topics.length !== 3) { if (l.topics.length !== 3) {
continue; continue;
@ -206,6 +216,7 @@ export const useTxData = (
value: BigNumber.from(l.data), value: BigNumber.from(l.data),
}); });
} }
}
// Extract token meta // Extract token meta
const tokenMetas: TokenMetas = {}; const tokenMetas: TokenMetas = {};
@ -227,31 +238,36 @@ export const useTxData = (
} }
setTxData({ setTxData({
transactionHash: _receipt.transactionHash, transactionHash: _response.hash,
status: _receipt.status === 1, from: _response.from,
blockNumber: _receipt.blockNumber, to: _response.to,
transactionIndex: _receipt.transactionIndex,
blockTransactionCount: _block.transactionCount,
confirmations: _receipt.confirmations,
timestamp: _block.timestamp,
miner: _block.miner,
from: _receipt.from,
to: _receipt.to,
createdContractAddress: _receipt.contractAddress,
value: _response.value, value: _response.value,
tokenTransfers, tokenTransfers,
tokenMetas, tokenMetas,
type: _response.type ?? 0, type: _response.type ?? 0,
fee: _response.gasPrice!.mul(_receipt.gasUsed),
blockBaseFeePerGas: _block.baseFeePerGas,
maxFeePerGas: _response.maxFeePerGas, maxFeePerGas: _response.maxFeePerGas,
maxPriorityFeePerGas: _response.maxPriorityFeePerGas, maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
gasPrice: _response.gasPrice!, gasPrice: _response.gasPrice!,
gasUsed: _receipt.gasUsed,
gasLimit: _response.gasLimit, gasLimit: _response.gasLimit,
nonce: _response.nonce, nonce: _response.nonce,
data: _response.data, data: _response.data,
confirmedData:
_receipt === null
? undefined
: {
status: _receipt.status === 1,
blockNumber: _receipt.blockNumber,
transactionIndex: _receipt.transactionIndex,
blockBaseFeePerGas: _block!.baseFeePerGas,
blockTransactionCount: _block!.transactionCount,
confirmations: _receipt.confirmations,
timestamp: _block!.timestamp,
miner: _block!.miner,
createdContractAddress: _receipt.contractAddress,
fee: _response.gasPrice!.mul(_receipt.gasUsed),
gasUsed: _receipt.gasUsed,
logs: _receipt.logs, logs: _receipt.logs,
},
}); });
}; };
readTxData(); readTxData();
@ -262,13 +278,13 @@ export const useTxData = (
export const useInternalOperations = ( export const useInternalOperations = (
provider: JsonRpcProvider | undefined, provider: JsonRpcProvider | undefined,
txData: TransactionData | undefined txData: TransactionData | undefined | null
): InternalOperation[] | undefined => { ): InternalOperation[] | undefined => {
const [intTransfers, setIntTransfers] = useState<InternalOperation[]>(); const [intTransfers, setIntTransfers] = useState<InternalOperation[]>();
useEffect(() => { useEffect(() => {
const traceTransfers = async () => { const traceTransfers = async () => {
if (!provider || !txData) { if (!provider || !txData || !txData.confirmedData) {
return; return;
} }