diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index d0bb9b8..c780485 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -49,27 +49,51 @@ const BlockTransactions: React.FC = () => { ]); document.title = `Block #${_block.number} Transactions | Otterscan`; - setTxs( - _block.transactions - .map((t, i) => { - return { - blockNumber: blockNumber.toNumber(), - timestamp: _block.timestamp, - idx: i, - hash: t.hash, - from: t.from, - to: t.to, - value: t.value, - fee: provider.formatter - .bigNumber(_receipts[i].gasUsed) - .mul(t.gasPrice!), - gasPrice: t.gasPrice!, - data: t.data, - status: provider.formatter.number(_receipts[i].status), - }; - }) - .reverse() + const responses = _block.transactions + .map((t, i): ProcessedTransaction => { + return { + blockNumber: blockNumber.toNumber(), + timestamp: _block.timestamp, + miner: _block.miner, + idx: i, + hash: t.hash, + from: t.from, + to: t.to, + value: t.value, + fee: provider.formatter + .bigNumber(_receipts[i].gasUsed) + .mul(t.gasPrice!), + gasPrice: t.gasPrice!, + data: t.data, + status: provider.formatter.number(_receipts[i].status), + }; + }) + .reverse(); + + const internalChecks = await Promise.all( + responses.map(async (res) => { + const r = await provider.send("ots_getTransactionTransfers", [ + res.hash, + ]); + for (const t of r) { + if ( + res.miner && + (res.miner === ethers.utils.getAddress(t.from) || + res.miner === ethers.utils.getAddress(t.to)) + ) { + return true; + } + } + return false; + }) ); + for (let i = 0; i < responses.length; i++) { + if (internalChecks[i]) { + responses[i].internalMinerInteraction = true; + } + } + + setTxs(responses); }; readBlock(); }, [blockNumber]); diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 2d95833..da0d33d 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -5,7 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheckCircle, faTimesCircle, - faAngleRight, faCaretRight, } from "@fortawesome/free-solid-svg-icons"; import { provider } from "./ethersconfig"; @@ -17,10 +16,18 @@ import BlockLink from "./components/BlockLink"; import AddressLink from "./components/AddressLink"; import Copy from "./components/Copy"; import Timestamp from "./components/Timestamp"; +import InternalTransfer from "./components/InternalTransfer"; import TokenLogo from "./components/TokenLogo"; import GasValue from "./components/GasValue"; import FormattedBalance from "./components/FormattedBalance"; import erc20 from "./erc20.json"; +import { + From, + TokenMetas, + TokenTransfer, + TransactionData, + Transfer, +} from "./types"; const USE_OTS = true; @@ -31,56 +38,6 @@ type TransactionParams = { txhash: string; }; -type TransactionData = { - transactionHash: string; - status: boolean; - blockNumber: number; - transactionIndex: number; - confirmations: number; - timestamp: number; - from: string; - to: string; - value: BigNumber; - tokenTransfers: TokenTransfer[]; - tokenMetas: TokenMetas; - fee: BigNumber; - gasPrice: BigNumber; - gasLimit: BigNumber; - gasUsed: BigNumber; - gasUsedPerc: number; - nonce: number; - data: string; - logs: ethers.providers.Log[]; -}; - -type From = { - current: string; - depth: number; -}; - -type Transfer = { - from: string; - to: string; - value: BigNumber; -}; - -type TokenTransfer = { - token: string; - from: string; - to: string; - value: BigNumber; -}; - -type TokenMeta = { - name: string; - symbol: string; - decimals: number; -}; - -type TokenMetas = { - [tokenAddress: string]: TokenMeta; -}; - const Transaction: React.FC = () => { const params = useParams(); const { txhash } = params; @@ -142,6 +99,7 @@ const Transaction: React.FC = () => { transactionIndex: _receipt.transactionIndex, confirmations: _receipt.confirmations, timestamp: _block.timestamp, + miner: _block.miner, from: _receipt.from, to: _receipt.to, value: _response.value, @@ -289,17 +247,11 @@ const Transaction: React.FC = () => { {transfers ? (
{transfers.map((t, i) => ( -
- - {" "} - TRANSFER - - {ethers.utils.formatEther(t.value)} Ether - From - - To - -
+ ))}
) : ( diff --git a/src/components/InternalTransfer.tsx b/src/components/InternalTransfer.tsx new file mode 100644 index 0000000..ac8f7d7 --- /dev/null +++ b/src/components/InternalTransfer.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { ethers } from "ethers"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faAngleRight, faCoins } from "@fortawesome/free-solid-svg-icons"; +import AddressLink from "./AddressLink"; +import { TransactionData, Transfer } from "../types"; + +type InternalTransferProps = { + txData: TransactionData; + transfer: Transfer; +}; + +const InternalTransfer: React.FC = ({ + txData, + transfer, +}) => { + const fromMiner = + txData.miner !== undefined && transfer.from === txData.miner; + const toMiner = txData.miner !== undefined && transfer.to === txData.miner; + + return ( +
+ + TRANSFER + + {ethers.utils.formatEther(transfer.value)} Ether + From +
+ {fromMiner && ( + + + + )} + +
+ To +
+ {toMiner && ( + + + + )} + +
+
+ ); +}; + +export default React.memo(InternalTransfer); diff --git a/src/components/TransactionDirection.tsx b/src/components/TransactionDirection.tsx index ee22e12..3695a77 100644 --- a/src/components/TransactionDirection.tsx +++ b/src/components/TransactionDirection.tsx @@ -1,6 +1,9 @@ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faLongArrowAltRight } from "@fortawesome/free-solid-svg-icons"; +import { + faCoins, + faLongArrowAltRight, +} from "@fortawesome/free-solid-svg-icons"; export enum Direction { IN, @@ -9,12 +12,18 @@ export enum Direction { INTERNAL, } +export enum Flags { + MINER, +} + type TransactionDirectionProps = { direction?: Direction; + flags?: Flags; }; const TransactionDirection: React.FC = ({ direction, + flags, }) => { let bgColor = "bg-green-50"; let fgColor = "text-green-500"; @@ -32,6 +41,12 @@ const TransactionDirection: React.FC = ({ msg = "SELF"; } else if (direction === Direction.INTERNAL) { msg = "INT"; + bgColor = "bg-green-100" + } + + if (flags === Flags.MINER) { + bgColor = "bg-yellow-50"; + fgColor = "text-yellow-400"; } return ( @@ -42,10 +57,14 @@ const TransactionDirection: React.FC = ({ : "w-5 h-5 rounded-full flex justify-center items-center" } text-xs font-bold`} > - {msg ?? ( - - - + {flags === Flags.MINER ? ( + + ) : ( + msg ?? ( + + + + ) )} ); diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index 4e74b35..57ce084 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -1,6 +1,9 @@ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons"; +import { + faExclamationCircle, + faCoins, +} from "@fortawesome/free-solid-svg-icons"; import MethodName from "../components/MethodName"; import BlockLink from "../components/BlockLink"; import TransactionLink from "../components/TransactionLink"; @@ -8,6 +11,7 @@ import AddressOrENSName from "../components/AddressOrENSName"; import TimestampAge from "../components/TimestampAge"; import TransactionDirection, { Direction, + Flags, } from "../components/TransactionDirection"; import TransactionValue from "../components/TransactionValue"; import { ENSReverseCache, ProcessedTransaction } from "../types"; @@ -42,9 +46,14 @@ const TransactionItem: React.FC = ({ const ensFrom = ensCache && tx.from && ensCache[tx.from]; const ensTo = ensCache && tx.to && ensCache[tx.to]; + const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction; return ( -
+
{tx.status === 0 && ( @@ -63,15 +72,25 @@ const TransactionItem: React.FC = ({ {tx.from && ( - +
+ {tx.miner && tx.miner === tx.from && ( + + + + )} + +
)}
- +
diff --git a/src/types.ts b/src/types.ts index 170fe58..36537f8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,14 @@ -import { BigNumber } from "ethers"; +import { ethers, BigNumber } from "ethers"; export type ProcessedTransaction = { blockNumber: number; timestamp: number; + miner?: string; idx: number; hash: string; from?: string; to?: string; + internalMinerInteraction?: boolean; value: BigNumber; fee: BigNumber; gasPrice: BigNumber; @@ -23,3 +25,54 @@ export type TransactionChunk = { export type ENSReverseCache = { [address: string]: string; }; + +export type TransactionData = { + transactionHash: string; + status: boolean; + blockNumber: number; + transactionIndex: number; + confirmations: number; + timestamp: number; + miner?: string; + from: string; + to: string; + value: BigNumber; + tokenTransfers: TokenTransfer[]; + tokenMetas: TokenMetas; + fee: BigNumber; + gasPrice: BigNumber; + gasLimit: BigNumber; + gasUsed: BigNumber; + gasUsedPerc: number; + nonce: number; + data: string; + logs: ethers.providers.Log[]; +}; + +export type From = { + current: string; + depth: number; +}; + +export type Transfer = { + from: string; + to: string; + value: BigNumber; +}; + +export type TokenTransfer = { + token: string; + from: string; + to: string; + value: BigNumber; +}; + +export type TokenMeta = { + name: string; + symbol: string; + decimals: number; +}; + +export type TokenMetas = { + [tokenAddress: string]: TokenMeta; +};