Squash pending tx initial implementation
This commit is contained in:
parent
6ac1528d36
commit
50f4abf23f
|
@ -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>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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 =
|
||||
|
|
28
src/types.ts
28
src/types.ts
|
@ -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[];
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue