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 StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle";
import ContentFrame from "./ContentFrame";
import Tab from "./components/Tab";
import Details from "./transaction/Details";
import Logs from "./transaction/Logs";
@ -28,7 +29,7 @@ const Transaction: React.FC = () => {
}
for (const t of internalOps) {
if (t.to === txData.miner) {
if (t.to === txData.confirmedData?.miner) {
return true;
}
}
@ -37,18 +38,30 @@ const Transaction: React.FC = () => {
const selectionCtx = useSelection();
const blockETHUSDPrice = useETHUSDOracle(provider, txData?.blockNumber);
const blockETHUSDPrice = useETHUSDOracle(
provider,
txData?.confirmedData?.blockNumber
);
return (
<StandardFrame>
<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 && (
<SelectionContext.Provider value={selectionCtx}>
<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}/logs`}>
Logs{txData && ` (${txData.logs.length})`}
</Tab>
{txData.confirmedData?.blockNumber !== undefined && (
<Tab href={`/tx/${txhash}/logs`}>
Logs{txData && ` (${txData.confirmedData?.logs?.length ?? 0})`}
</Tab>
)}
</div>
<Switch>
<Route path="/tx/:txhash/" exact>

View File

@ -22,7 +22,9 @@ const InternalSelfDestruct: React.FC<InternalSelfDestructProps> = ({
const { provider } = useContext(RuntimeContext);
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 (
<>

View File

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

View File

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

View File

@ -10,8 +10,8 @@ type LogsProps = {
const Logs: React.FC<LogsProps> = ({ txData }) => (
<ContentFrame tabs>
<div className="text-sm py-4">Transaction Receipt Event Logs</div>
{txData &&
txData.logs.map((l, i) => (
{txData.confirmedData &&
txData.confirmedData.logs.map((l, i) => (
<div className="flex space-x-10 py-5" key={i}>
<div>
<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">
<DecoratedAddressLink
address={l.address}
miner={l.address === txData.miner}
miner={l.address === txData.confirmedData?.miner}
txFrom={l.address === txData.from}
txTo={l.address === txData.to}
/>

View File

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

View File

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

View File

@ -174,8 +174,8 @@ export const useBlockData = (
export const useTxData = (
provider: JsonRpcProvider | undefined,
txhash: string
): TransactionData | undefined => {
const [txData, setTxData] = useState<TransactionData>();
): TransactionData | undefined | null => {
const [txData, setTxData] = useState<TransactionData | undefined | null>();
useEffect(() => {
if (!provider) {
@ -187,24 +187,35 @@ export const useTxData = (
provider.getTransaction(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`;
// Extract token transfers
const tokenTransfers: TokenTransfer[] = [];
for (const l of _receipt.logs) {
if (l.topics.length !== 3) {
continue;
if (_receipt) {
for (const l of _receipt.logs) {
if (l.topics.length !== 3) {
continue;
}
if (l.topics[0] !== TRANSFER_TOPIC) {
continue;
}
tokenTransfers.push({
token: l.address,
from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)),
to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)),
value: BigNumber.from(l.data),
});
}
if (l.topics[0] !== TRANSFER_TOPIC) {
continue;
}
tokenTransfers.push({
token: l.address,
from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)),
to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)),
value: BigNumber.from(l.data),
});
}
// Extract token meta
@ -227,31 +238,36 @@ export const useTxData = (
}
setTxData({
transactionHash: _receipt.transactionHash,
status: _receipt.status === 1,
blockNumber: _receipt.blockNumber,
transactionIndex: _receipt.transactionIndex,
blockTransactionCount: _block.transactionCount,
confirmations: _receipt.confirmations,
timestamp: _block.timestamp,
miner: _block.miner,
from: _receipt.from,
to: _receipt.to,
createdContractAddress: _receipt.contractAddress,
transactionHash: _response.hash,
from: _response.from,
to: _response.to,
value: _response.value,
tokenTransfers,
tokenMetas,
type: _response.type ?? 0,
fee: _response.gasPrice!.mul(_receipt.gasUsed),
blockBaseFeePerGas: _block.baseFeePerGas,
maxFeePerGas: _response.maxFeePerGas,
maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
gasPrice: _response.gasPrice!,
gasUsed: _receipt.gasUsed,
gasLimit: _response.gasLimit,
nonce: _response.nonce,
data: _response.data,
logs: _receipt.logs,
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,
},
});
};
readTxData();
@ -262,13 +278,13 @@ export const useTxData = (
export const useInternalOperations = (
provider: JsonRpcProvider | undefined,
txData: TransactionData | undefined
txData: TransactionData | undefined | null
): InternalOperation[] | undefined => {
const [intTransfers, setIntTransfers] = useState<InternalOperation[]>();
useEffect(() => {
const traceTransfers = async () => {
if (!provider || !txData) {
if (!provider || !txData || !txData.confirmedData) {
return;
}