Merge branch 'feature/hover-highlight' into develop

This commit is contained in:
Willian Mitsuda 2021-07-14 16:59:31 -03:00
commit 8477feab77
14 changed files with 467 additions and 313 deletions

View File

@ -15,6 +15,7 @@ import { SearchController } from "./search/search";
import { RuntimeContext } from "./useRuntime";
import { useENSCache } from "./useReverseCache";
import { useFeeToggler } from "./search/useFeeToggler";
import { SelectionContext, useSelection } from "./useSelection";
type BlockParams = {
addressOrName: string;
@ -153,6 +154,8 @@ const AddressTransactions: React.FC = () => {
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
const selectionCtx = useSelection();
return (
<StandardFrame>
{error ? (
@ -204,7 +207,7 @@ const AddressTransactions: React.FC = () => {
feeDisplayToggler={feeDisplayToggler}
/>
{controller ? (
<>
<SelectionContext.Provider value={selectionCtx}>
{controller.getPage().map((tx) => (
<TransactionItem
key={tx.hash}
@ -228,7 +231,7 @@ const AddressTransactions: React.FC = () => {
nextHash={page ? page[page.length - 1].hash : ""}
/>
</div>
</>
</SelectionContext.Provider>
) : (
<PendingResults />
)}

View File

@ -0,0 +1,21 @@
import React from "react";
import { ethers } from "ethers";
import StandardSubtitle from "./StandardSubtitle";
import BlockLink from "./components/BlockLink";
type BlockTransactionHeaderProps = {
blockTag: ethers.providers.BlockTag;
};
const BlockTransactionHeader: React.FC<BlockTransactionHeaderProps> = ({
blockTag,
}) => (
<>
<StandardSubtitle>Transactions</StandardSubtitle>
<div className="pb-2 text-sm text-gray-500">
For Block <BlockLink blockTag={blockTag} />
</div>
</>
);
export default React.memo(BlockTransactionHeader);

View File

@ -0,0 +1,78 @@
import React, { useContext } from "react";
import ContentFrame from "./ContentFrame";
import PageControl from "./search/PageControl";
import ResultHeader from "./search/ResultHeader";
import PendingResults from "./search/PendingResults";
import TransactionItem from "./search/TransactionItem";
import { useFeeToggler } from "./search/useFeeToggler";
import { RuntimeContext } from "./useRuntime";
import { SelectionContext, useSelection } from "./useSelection";
import { useENSCache } from "./useReverseCache";
import { ProcessedTransaction } from "./types";
import { PAGE_SIZE } from "./params";
type BlockTransactionResultsProps = {
page?: ProcessedTransaction[];
total: number;
pageNumber: number;
};
const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
page,
total,
pageNumber,
}) => {
const selectionCtx = useSelection();
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
const { provider } = useContext(RuntimeContext);
const reverseCache = useENSCache(provider, page);
return (
<ContentFrame>
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>A total of {total} transactions found</>
)}
</div>
<PageControl
pageNumber={pageNumber}
pageSize={PAGE_SIZE}
total={total}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{page ? (
<SelectionContext.Provider value={selectionCtx}>
{page.map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
ensCache={reverseCache}
feeDisplay={feeDisplay}
/>
))}
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
A total of {total} transactions found
</div>
<PageControl
pageNumber={pageNumber}
pageSize={PAGE_SIZE}
total={total}
/>
</div>
</SelectionContext.Provider>
) : (
<PendingResults />
)}
</ContentFrame>
);
};
export default React.memo(BlockTransactionResults);

View File

@ -3,18 +3,11 @@ import { useParams, useLocation } from "react-router";
import { ethers } from "ethers";
import queryString from "query-string";
import StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle";
import ContentFrame from "./ContentFrame";
import PageControl from "./search/PageControl";
import ResultHeader from "./search/ResultHeader";
import PendingResults from "./search/PendingResults";
import TransactionItem from "./search/TransactionItem";
import BlockLink from "./components/BlockLink";
import BlockTransactionHeader from "./BlockTransactionHeader";
import BlockTransactionResults from "./BlockTransactionResults";
import { ProcessedTransaction } from "./types";
import { PAGE_SIZE } from "./params";
import { useFeeToggler } from "./search/useFeeToggler";
import { RuntimeContext } from "./useRuntime";
import { useENSCache } from "./useReverseCache";
type BlockParams = {
blockNumber: string;
@ -110,62 +103,16 @@ const BlockTransactions: React.FC = () => {
}, [txs, pageNumber]);
const total = useMemo(() => txs?.length ?? 0, [txs]);
const reverseCache = useENSCache(provider, page);
document.title = `Block #${blockNumber} Txns | Otterscan`;
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
return (
<StandardFrame>
<StandardSubtitle>Transactions</StandardSubtitle>
<div className="pb-2 text-sm text-gray-500">
For Block <BlockLink blockTag={blockNumber.toNumber()} />
</div>
<ContentFrame>
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
{page === undefined ? (
<>Waiting for search results...</>
) : (
<>A total of {total} transactions found</>
)}
</div>
<PageControl
pageNumber={pageNumber}
pageSize={PAGE_SIZE}
total={total}
/>
</div>
<ResultHeader
feeDisplay={feeDisplay}
feeDisplayToggler={feeDisplayToggler}
/>
{page ? (
<>
{page.map((tx) => (
<TransactionItem
key={tx.hash}
tx={tx}
ensCache={reverseCache}
feeDisplay={feeDisplay}
/>
))}
<div className="flex justify-between items-baseline py-3">
<div className="text-sm text-gray-500">
A total of {total} transactions found
</div>
<PageControl
pageNumber={pageNumber}
pageSize={PAGE_SIZE}
total={total}
/>
</div>
</>
) : (
<PendingResults />
)}
</ContentFrame>
<BlockTransactionHeader blockTag={blockNumber.toNumber()} />
<BlockTransactionResults
page={page}
total={total}
pageNumber={pageNumber}
/>
</StandardFrame>
);
};

View File

@ -1,8 +1,9 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCaretRight } from "@fortawesome/free-solid-svg-icons";
import AddressLink from "./components/AddressLink";
import AddressHighlighter from "./components/AddressHighlighter";
import AddressOrENSName from "./components/AddressOrENSName";
import AddressLink from "./components/AddressLink";
import TokenLogo from "./components/TokenLogo";
import FormattedBalance from "./components/FormattedBalance";
import { TokenMetas, TokenTransfer } from "./types";
@ -20,16 +21,20 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
<span className="text-gray-500">
<FontAwesomeIcon icon={faCaretRight} size="1x" />
</span>
<div className="grid grid-cols-5">
<div className="flex space-x-2">
<div className="grid grid-cols-5 gap-x-1">
<div className="flex space-x-1">
<span className="font-bold">From</span>
<AddressOrENSName address={t.from} />
<AddressHighlighter address={t.from}>
<AddressOrENSName address={t.from} />
</AddressHighlighter>
</div>
<div className="flex space-x-2">
<div className="flex space-x-1">
<span className="font-bold">To</span>
<AddressOrENSName address={t.to} />
<AddressHighlighter address={t.to}>
<AddressOrENSName address={t.to} />
</AddressHighlighter>
</div>
<div className="col-span-3 flex space-x-2">
<div className="col-span-3 flex space-x-1">
<span className="font-bold">For</span>
<span>
<FormattedBalance

View File

@ -7,28 +7,15 @@ import React, {
} from "react";
import { Route, Switch, useParams } from "react-router-dom";
import { BigNumber, ethers } from "ethers";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheckCircle,
faTimesCircle,
} from "@fortawesome/free-solid-svg-icons";
import StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle";
import Tab from "./components/Tab";
import ContentFrame from "./ContentFrame";
import BlockLink from "./components/BlockLink";
import AddressOrENSName from "./components/AddressOrENSName";
import AddressLink from "./components/AddressLink";
import Copy from "./components/Copy";
import Timestamp from "./components/Timestamp";
import InternalTransfer from "./components/InternalTransfer";
import MethodName from "./components/MethodName";
import GasValue from "./components/GasValue";
import FormattedBalance from "./components/FormattedBalance";
import TokenTransferItem from "./TokenTransferItem";
import Details from "./transaction/Details";
import Logs from "./transaction/Logs";
import erc20 from "./erc20.json";
import { TokenMetas, TokenTransfer, TransactionData, Transfer } from "./types";
import { RuntimeContext } from "./useRuntime";
import { SelectionContext, useSelection } from "./useSelection";
const TRANSFER_TOPIC =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@ -137,7 +124,7 @@ const Transaction: React.FC = () => {
return false;
}, [txData, transfers]);
const traceTransfersUsingOtsTrace = useCallback(async () => {
const traceTransfers = useCallback(async () => {
if (!provider || !txData) {
return;
}
@ -157,14 +144,16 @@ const Transaction: React.FC = () => {
setTransfers(_transfers);
}, [provider, txData]);
useEffect(() => {
traceTransfersUsingOtsTrace();
}, [traceTransfersUsingOtsTrace]);
traceTransfers();
}, [traceTransfers]);
const selectionCtx = useSelection();
return (
<StandardFrame>
<StandardSubtitle>Transaction Details</StandardSubtitle>
{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`}>
@ -173,198 +162,20 @@ const Transaction: React.FC = () => {
</div>
<Switch>
<Route path="/tx/:txhash/" exact>
<ContentFrame tabs>
<InfoRow title="Transaction Hash">
<div className="flex items-baseline space-x-2">
<span className="font-hash">{txData.transactionHash}</span>
<Copy value={txData.transactionHash} />
</div>
</InfoRow>
<InfoRow title="Status">
{txData.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>
</span>
) : (
<span className="flex items-center w-min rounded-lg space-x-1 px-3 py-1 bg-red-50 text-red-500 text-xs">
<FontAwesomeIcon icon={faTimesCircle} size="1x" />
<span>Fail</span>
</span>
)}
</InfoRow>
<InfoRow title="Block">
<div className="flex items-baseline space-x-2">
<BlockLink blockTag={txData.blockNumber} />
<span className="rounded text-xs bg-gray-100 text-gray-500 px-2 py-1">
{txData.confirmations} Block Confirmations
</span>
</div>
</InfoRow>
<InfoRow title="Timestamp">
<Timestamp value={txData.timestamp} />
</InfoRow>
<InfoRow title="From">
<div className="flex items-baseline space-x-2">
<AddressOrENSName
address={txData.from}
minerAddress={txData.miner}
/>
<Copy value={txData.from} />
</div>
</InfoRow>
<InfoRow title="Interacted With (To)">
<div className="flex items-baseline space-x-2">
<AddressOrENSName
address={txData.to}
minerAddress={txData.miner}
/>
<Copy value={txData.to} />
</div>
{transfers && (
<div className="mt-2 space-y-1">
{transfers.map((t, i) => (
<InternalTransfer
key={i}
txData={txData}
transfer={t}
/>
))}
</div>
)}
</InfoRow>
<InfoRow title="Transaction Action">
<MethodName data={txData.data} />
</InfoRow>
{txData.tokenTransfers.length > 0 && (
<InfoRow
title={`Tokens Transferred (${txData.tokenTransfers.length})`}
>
<div className="space-y-2">
{txData.tokenTransfers.map((t, i) => (
<TokenTransferItem
key={i}
t={t}
tokenMetas={txData.tokenMetas}
/>
))}
</div>
</InfoRow>
)}
<InfoRow title="Value">
<span className="rounded bg-gray-100 px-2 py-1 text-xs">
{ethers.utils.formatEther(txData.value)} Ether
</span>
</InfoRow>
<InfoRow title="Transaction Fee">
<FormattedBalance value={txData.fee} /> Ether
</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
</span>
)}
</div>
</InfoRow>
<InfoRow title="Ether Price">N/A</InfoRow>
<InfoRow title="Gas Limit">
<GasValue value={txData.gasLimit} />
</InfoRow>
<InfoRow title="Gas Used by Transaction">
<GasValue value={txData.gasUsed} /> (
{(txData.gasUsedPerc * 100).toFixed(2)}%)
</InfoRow>
<InfoRow title="Nonce">{txData.nonce}</InfoRow>
<InfoRow title="Position in Block">
<span className="rounded px-2 py-1 bg-gray-100 text-gray-500 text-xs">
{txData.transactionIndex}
</span>
</InfoRow>
<InfoRow title="Input Data">
<textarea
className="w-full h-40 bg-gray-50 text-gray-500 font-mono focus:outline-none border rounded p-2"
value={txData.data}
readOnly
/>
</InfoRow>
</ContentFrame>
<Details
txData={txData}
transfers={transfers}
sendsEthToMiner={sendsEthToMiner}
/>
</Route>
<Route path="/tx/:txhash/logs/" exact>
<ContentFrame tabs>
<div className="text-sm py-4">
Transaction Receipt Event Logs
</div>
{txData &&
txData.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">
{l.logIndex}
</span>
</div>
<div className="w-full space-y-2">
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
<div className="font-bold text-right">Address</div>
<div className="col-span-11">
<AddressLink address={l.address} />
</div>
</div>
{l.topics.map((t, i) => (
<div
className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm"
key={i}
>
<div className="text-right">
{i === 0 && "Topics"}
</div>
<div className="flex space-x-2 items-center col-span-11 font-mono">
<span className="rounded bg-gray-100 text-gray-500 px-2 py-1 text-xs">
{i}
</span>
<span>{t}</span>
</div>
</div>
))}
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
<div className="text-right pt-2">Data</div>
<div className="col-span-11">
<textarea
className="w-full h-20 bg-gray-50 font-mono focus:outline-none border rounded p-2"
value={l.data}
/>
</div>
</div>
</div>
</div>
))}
</ContentFrame>
<Logs txData={txData} />
</Route>
</Switch>
</>
</SelectionContext.Provider>
)}
</StandardFrame>
);
};
type InfoRowProps = {
title: string;
};
const InfoRow: React.FC<InfoRowProps> = ({ title, children }) => (
<div className="grid grid-cols-4 py-4 text-sm">
<div>{title}:</div>
<div className="col-span-3">{children}</div>
</div>
);
export default React.memo(Transaction);

View File

@ -0,0 +1,37 @@
import React from "react";
import { useSelectionContext } from "../useSelection";
type AddressHighlighterProps = React.PropsWithChildren<{
address: string;
}>;
const AddressHighlighter: React.FC<AddressHighlighterProps> = ({
address,
children,
}) => {
const [selection, setSelection] = useSelectionContext();
const select = () => {
setSelection({ type: "address", content: address });
};
const deselect = () => {
setSelection(null);
};
return (
<div
className={`border border-dashed rounded hover:bg-transparent hover:border-transparent px-1 truncate ${
selection !== null &&
selection.type === "address" &&
selection.content === address
? "border-orange-400 bg-yellow-100"
: "border-transparent"
}`}
onMouseEnter={select}
onMouseLeave={deselect}
>
{children}
</div>
);
};
export default React.memo(AddressHighlighter);

View File

@ -0,0 +1,14 @@
import React from "react";
type InfoRowProps = React.PropsWithChildren<{
title: string;
}>;
const InfoRow: React.FC<InfoRowProps> = ({ title, children }) => (
<div className="grid grid-cols-4 py-4 text-sm">
<div>{title}:</div>
<div className="col-span-3">{children}</div>
</div>
);
export default React.memo(InfoRow);

View File

@ -2,6 +2,7 @@ import React from "react";
import { ethers } from "ethers";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleRight, faCoins } from "@fortawesome/free-solid-svg-icons";
import AddressHighlighter from "./AddressHighlighter";
import AddressLink from "./AddressLink";
import { TransactionData, Transfer } from "../types";
@ -24,31 +25,39 @@ const InternalTransfer: React.FC<InternalTransferProps> = ({
<FontAwesomeIcon icon={faAngleRight} size="1x" /> TRANSFER
</span>
<span>{ethers.utils.formatEther(transfer.value)} Ether</span>
<span className="text-gray-500">From</span>
<div
className={`flex items-baseline space-x-1 ${
fromMiner ? "rounded px-2 py-1 bg-yellow-100" : ""
}`}
>
{fromMiner && (
<span className="text-yellow-400" title="Miner address">
<FontAwesomeIcon icon={faCoins} size="1x" />
</span>
)}
<AddressLink address={transfer.from} />
<div className="flex items-baseline">
<span className="text-gray-500">From</span>
<AddressHighlighter address={transfer.from}>
<div
className={`flex items-baseline space-x-1 ${
fromMiner ? "rounded px-2 py-1 bg-yellow-100" : ""
}`}
>
{fromMiner && (
<span className="text-yellow-400" title="Miner address">
<FontAwesomeIcon icon={faCoins} size="1x" />
</span>
)}
<AddressLink address={transfer.from} />
</div>
</AddressHighlighter>
</div>
<span className="text-gray-500">To</span>
<div
className={`flex items-baseline space-x-1 px-2 py-1 ${
toMiner ? "rounded px-2 py-1 bg-yellow-100" : ""
}`}
>
{toMiner && (
<span className="text-yellow-400" title="Miner address">
<FontAwesomeIcon icon={faCoins} size="1x" />
</span>
)}
<AddressLink address={transfer.to} />
<div className="flex items-baseline">
<span className="text-gray-500">To</span>
<AddressHighlighter address={transfer.to}>
<div
className={`flex items-baseline space-x-1 ${
toMiner ? "rounded px-2 py-1 bg-yellow-100" : ""
}`}
>
{toMiner && (
<span className="text-yellow-400" title="Miner address">
<FontAwesomeIcon icon={faCoins} size="1x" />
</span>
)}
<AddressLink address={transfer.to} />
</div>
</AddressHighlighter>
</div>
</div>
);

View File

@ -15,8 +15,8 @@ const ResultHeader: React.FC<ResultHeaderProps> = ({
<div>Method</div>
<div>Block</div>
<div>Age</div>
<div className="col-span-2">From</div>
<div className="col-span-2">To</div>
<div className="col-span-2 ml-1">From</div>
<div className="col-span-2 ml-1">To</div>
<div className="col-span-2">Value</div>
<div>
<button

View File

@ -6,6 +6,7 @@ import BlockLink from "../components/BlockLink";
import TransactionLink from "../components/TransactionLink";
import AddressOrENSName from "../components/AddressOrENSName";
import TimestampAge from "../components/TimestampAge";
import AddressHighlighter from "../components/AddressHighlighter";
import TransactionDirection, {
Direction,
Flags,
@ -69,12 +70,14 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
<span className="col-span-2 flex justify-between items-baseline space-x-2 pr-2">
<span className="truncate" title={tx.from}>
{tx.from && (
<AddressOrENSName
address={tx.from}
ensName={ensFrom}
selectedAddress={selectedAddress}
minerAddress={tx.miner}
/>
<AddressHighlighter address={tx.from}>
<AddressOrENSName
address={tx.from}
ensName={ensFrom}
selectedAddress={selectedAddress}
minerAddress={tx.miner}
/>
</AddressHighlighter>
)}
</span>
<span>
@ -86,12 +89,14 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
</span>
<span className="col-span-2 truncate" title={tx.to}>
{tx.to && (
<AddressOrENSName
address={tx.to}
ensName={ensTo}
selectedAddress={selectedAddress}
minerAddress={tx.miner}
/>
<AddressHighlighter address={tx.to}>
<AddressOrENSName
address={tx.to}
ensName={ensTo}
selectedAddress={selectedAddress}
minerAddress={tx.miner}
/>
</AddressHighlighter>
)}
</span>
<span className="col-span-2 truncate">

144
src/transaction/Details.tsx Normal file
View File

@ -0,0 +1,144 @@
import React from "react";
import { ethers } from "ethers";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheckCircle,
faTimesCircle,
} from "@fortawesome/free-solid-svg-icons";
import ContentFrame from "../ContentFrame";
import InfoRow from "../components/InfoRow";
import BlockLink from "../components/BlockLink";
import AddressHighlighter from "../components/AddressHighlighter";
import AddressOrENSName from "../components/AddressOrENSName";
import Copy from "../components/Copy";
import Timestamp from "../components/Timestamp";
import InternalTransfer from "../components/InternalTransfer";
import MethodName from "../components/MethodName";
import GasValue from "../components/GasValue";
import FormattedBalance from "../components/FormattedBalance";
import TokenTransferItem from "../TokenTransferItem";
import { TransactionData, Transfer } from "../types";
type DetailsProps = {
txData: TransactionData;
transfers?: Transfer[];
sendsEthToMiner: boolean;
};
const Details: React.FC<DetailsProps> = ({
txData,
transfers,
sendsEthToMiner,
}) => (
<ContentFrame tabs>
<InfoRow title="Transaction Hash">
<div className="flex items-baseline space-x-2">
<span className="font-hash">{txData.transactionHash}</span>
<Copy value={txData.transactionHash} />
</div>
</InfoRow>
<InfoRow title="Status">
{txData.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>
</span>
) : (
<span className="flex items-center w-min rounded-lg space-x-1 px-3 py-1 bg-red-50 text-red-500 text-xs">
<FontAwesomeIcon icon={faTimesCircle} size="1x" />
<span>Fail</span>
</span>
)}
</InfoRow>
<InfoRow title="Block">
<div className="flex items-baseline space-x-2">
<BlockLink blockTag={txData.blockNumber} />
<span className="rounded text-xs bg-gray-100 text-gray-500 px-2 py-1">
{txData.confirmations} Block Confirmations
</span>
</div>
</InfoRow>
<InfoRow title="Timestamp">
<Timestamp value={txData.timestamp} />
</InfoRow>
<InfoRow title="From">
<div className="flex items-baseline space-x-2 -ml-1">
<AddressHighlighter address={txData.from}>
<AddressOrENSName address={txData.from} minerAddress={txData.miner} />
</AddressHighlighter>
<Copy value={txData.from} />
</div>
</InfoRow>
<InfoRow title="Interacted With (To)">
<div className="flex items-baseline space-x-2 -ml-1">
<AddressHighlighter address={txData.to}>
<AddressOrENSName address={txData.to} minerAddress={txData.miner} />
</AddressHighlighter>
<Copy value={txData.to} />
</div>
{transfers && (
<div className="mt-2 space-y-1">
{transfers.map((t, i) => (
<InternalTransfer key={i} txData={txData} transfer={t} />
))}
</div>
)}
</InfoRow>
<InfoRow title="Transaction Action">
<MethodName data={txData.data} />
</InfoRow>
{txData.tokenTransfers.length > 0 && (
<InfoRow title={`Tokens Transferred (${txData.tokenTransfers.length})`}>
<div className="space-y-2">
{txData.tokenTransfers.map((t, i) => (
<TokenTransferItem key={i} t={t} tokenMetas={txData.tokenMetas} />
))}
</div>
</InfoRow>
)}
<InfoRow title="Value">
<span className="rounded bg-gray-100 px-2 py-1 text-xs">
{ethers.utils.formatEther(txData.value)} Ether
</span>
</InfoRow>
<InfoRow title="Transaction Fee">
<FormattedBalance value={txData.fee} /> Ether
</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
</span>
)}
</div>
</InfoRow>
<InfoRow title="Ether Price">N/A</InfoRow>
<InfoRow title="Gas Limit">
<GasValue value={txData.gasLimit} />
</InfoRow>
<InfoRow title="Gas Used by Transaction">
<GasValue value={txData.gasUsed} /> (
{(txData.gasUsedPerc * 100).toFixed(2)}%)
</InfoRow>
<InfoRow title="Nonce">{txData.nonce}</InfoRow>
<InfoRow title="Position in Block">
<span className="rounded px-2 py-1 bg-gray-100 text-gray-500 text-xs">
{txData.transactionIndex}
</span>
</InfoRow>
<InfoRow title="Input Data">
<textarea
className="w-full h-40 bg-gray-50 text-gray-500 font-mono focus:outline-none border rounded p-2"
value={txData.data}
readOnly
/>
</InfoRow>
</ContentFrame>
);
export default React.memo(Details);

57
src/transaction/Logs.tsx Normal file
View File

@ -0,0 +1,57 @@
import React from "react";
import ContentFrame from "../ContentFrame";
import AddressLink from "../components/AddressLink";
import { TransactionData } from "../types";
type LogsProps = {
txData: TransactionData;
};
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) => (
<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">
{l.logIndex}
</span>
</div>
<div className="w-full space-y-2">
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
<div className="font-bold text-right">Address</div>
<div className="col-span-11">
<AddressLink address={l.address} />
</div>
</div>
{l.topics.map((t, i) => (
<div
className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm"
key={i}
>
<div className="text-right">{i === 0 && "Topics"}</div>
<div className="flex space-x-2 items-center col-span-11 font-mono">
<span className="rounded bg-gray-100 text-gray-500 px-2 py-1 text-xs">
{i}
</span>
<span>{t}</span>
</div>
</div>
))}
<div className="grid grid-cols-12 gap-x-3 gap-y-5 text-sm">
<div className="text-right pt-2">Data</div>
<div className="col-span-11">
<textarea
className="w-full h-20 bg-gray-50 font-mono focus:outline-none border rounded p-2"
value={l.data}
/>
</div>
</div>
</div>
</div>
))}
</ContentFrame>
);
export default React.memo(Logs);

23
src/useSelection.ts Normal file
View File

@ -0,0 +1,23 @@
import React, { useState, useContext } from "react";
export type Selection = {
type: string;
content: string;
};
export const useSelection = (): [
Selection | null,
React.Dispatch<React.SetStateAction<Selection | null>>
] => {
const [selection, setSelection] = useState<Selection | null>(null);
return [selection, setSelection];
};
export const SelectionContext = React.createContext<
ReturnType<typeof useSelection>
>(null!);
export const useSelectionContext = () => {
const ctx = useContext(SelectionContext);
return ctx;
};