Merge branch 'feature/flash-transactions' into develop

This commit is contained in:
Willian Mitsuda 2021-07-05 00:46:32 -03:00
commit 646d345215
6 changed files with 220 additions and 96 deletions

View File

@ -49,27 +49,51 @@ const BlockTransactions: React.FC = () => {
]); ]);
document.title = `Block #${_block.number} Transactions | Otterscan`; document.title = `Block #${_block.number} Transactions | Otterscan`;
setTxs( const responses = _block.transactions
_block.transactions .map((t, i): ProcessedTransaction => {
.map((t, i) => { return {
return { blockNumber: blockNumber.toNumber(),
blockNumber: blockNumber.toNumber(), timestamp: _block.timestamp,
timestamp: _block.timestamp, miner: _block.miner,
idx: i, idx: i,
hash: t.hash, hash: t.hash,
from: t.from, from: t.from,
to: t.to, to: t.to,
value: t.value, value: t.value,
fee: provider.formatter fee: provider.formatter
.bigNumber(_receipts[i].gasUsed) .bigNumber(_receipts[i].gasUsed)
.mul(t.gasPrice!), .mul(t.gasPrice!),
gasPrice: t.gasPrice!, gasPrice: t.gasPrice!,
data: t.data, data: t.data,
status: provider.formatter.number(_receipts[i].status), status: provider.formatter.number(_receipts[i].status),
}; };
}) })
.reverse() .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(); readBlock();
}, [blockNumber]); }, [blockNumber]);

View File

@ -5,7 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import {
faCheckCircle, faCheckCircle,
faTimesCircle, faTimesCircle,
faAngleRight,
faCaretRight, faCaretRight,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { provider } from "./ethersconfig"; import { provider } from "./ethersconfig";
@ -17,10 +16,18 @@ import BlockLink from "./components/BlockLink";
import AddressLink from "./components/AddressLink"; import AddressLink from "./components/AddressLink";
import Copy from "./components/Copy"; import Copy from "./components/Copy";
import Timestamp from "./components/Timestamp"; import Timestamp from "./components/Timestamp";
import InternalTransfer from "./components/InternalTransfer";
import TokenLogo from "./components/TokenLogo"; import TokenLogo from "./components/TokenLogo";
import GasValue from "./components/GasValue"; import GasValue from "./components/GasValue";
import FormattedBalance from "./components/FormattedBalance"; import FormattedBalance from "./components/FormattedBalance";
import erc20 from "./erc20.json"; import erc20 from "./erc20.json";
import {
From,
TokenMetas,
TokenTransfer,
TransactionData,
Transfer,
} from "./types";
const USE_OTS = true; const USE_OTS = true;
@ -31,56 +38,6 @@ type TransactionParams = {
txhash: string; 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 Transaction: React.FC = () => {
const params = useParams<TransactionParams>(); const params = useParams<TransactionParams>();
const { txhash } = params; const { txhash } = params;
@ -142,6 +99,7 @@ const Transaction: React.FC = () => {
transactionIndex: _receipt.transactionIndex, transactionIndex: _receipt.transactionIndex,
confirmations: _receipt.confirmations, confirmations: _receipt.confirmations,
timestamp: _block.timestamp, timestamp: _block.timestamp,
miner: _block.miner,
from: _receipt.from, from: _receipt.from,
to: _receipt.to, to: _receipt.to,
value: _response.value, value: _response.value,
@ -289,17 +247,11 @@ const Transaction: React.FC = () => {
{transfers ? ( {transfers ? (
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
{transfers.map((t, i) => ( {transfers.map((t, i) => (
<div key={i} className="flex space-x-1 text-xs"> <InternalTransfer
<span className="text-gray-500"> key={i}
<FontAwesomeIcon icon={faAngleRight} size="1x" />{" "} txData={txData}
TRANSFER transfer={t}
</span> />
<span>{ethers.utils.formatEther(t.value)} Ether</span>
<span className="text-gray-500">From</span>
<AddressLink address={t.from} />
<span className="text-gray-500">To</span>
<AddressLink address={t.to} />
</div>
))} ))}
</div> </div>
) : ( ) : (

View File

@ -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<InternalTransferProps> = ({
txData,
transfer,
}) => {
const fromMiner =
txData.miner !== undefined && transfer.from === txData.miner;
const toMiner = txData.miner !== undefined && transfer.to === txData.miner;
return (
<div className="flex items-baseline space-x-1 text-xs">
<span className="text-gray-500">
<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>
<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>
</div>
);
};
export default React.memo(InternalTransfer);

View File

@ -1,6 +1,9 @@
import React from "react"; import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 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 { export enum Direction {
IN, IN,
@ -9,12 +12,18 @@ export enum Direction {
INTERNAL, INTERNAL,
} }
export enum Flags {
MINER,
}
type TransactionDirectionProps = { type TransactionDirectionProps = {
direction?: Direction; direction?: Direction;
flags?: Flags;
}; };
const TransactionDirection: React.FC<TransactionDirectionProps> = ({ const TransactionDirection: React.FC<TransactionDirectionProps> = ({
direction, direction,
flags,
}) => { }) => {
let bgColor = "bg-green-50"; let bgColor = "bg-green-50";
let fgColor = "text-green-500"; let fgColor = "text-green-500";
@ -32,6 +41,12 @@ const TransactionDirection: React.FC<TransactionDirectionProps> = ({
msg = "SELF"; msg = "SELF";
} else if (direction === Direction.INTERNAL) { } else if (direction === Direction.INTERNAL) {
msg = "INT"; msg = "INT";
bgColor = "bg-green-100"
}
if (flags === Flags.MINER) {
bgColor = "bg-yellow-50";
fgColor = "text-yellow-400";
} }
return ( return (
@ -42,10 +57,14 @@ const TransactionDirection: React.FC<TransactionDirectionProps> = ({
: "w-5 h-5 rounded-full flex justify-center items-center" : "w-5 h-5 rounded-full flex justify-center items-center"
} text-xs font-bold`} } text-xs font-bold`}
> >
{msg ?? ( {flags === Flags.MINER ? (
<span> <FontAwesomeIcon icon={faCoins} size="1x" />
<FontAwesomeIcon icon={faLongArrowAltRight} /> ) : (
</span> msg ?? (
<span>
<FontAwesomeIcon icon={faLongArrowAltRight} />
</span>
)
)} )}
</span> </span>
); );

View File

@ -1,6 +1,9 @@
import React from "react"; import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 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 MethodName from "../components/MethodName";
import BlockLink from "../components/BlockLink"; import BlockLink from "../components/BlockLink";
import TransactionLink from "../components/TransactionLink"; import TransactionLink from "../components/TransactionLink";
@ -8,6 +11,7 @@ import AddressOrENSName from "../components/AddressOrENSName";
import TimestampAge from "../components/TimestampAge"; import TimestampAge from "../components/TimestampAge";
import TransactionDirection, { import TransactionDirection, {
Direction, Direction,
Flags,
} from "../components/TransactionDirection"; } from "../components/TransactionDirection";
import TransactionValue from "../components/TransactionValue"; import TransactionValue from "../components/TransactionValue";
import { ENSReverseCache, ProcessedTransaction } from "../types"; import { ENSReverseCache, ProcessedTransaction } from "../types";
@ -42,9 +46,14 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
const ensFrom = ensCache && tx.from && ensCache[tx.from]; const ensFrom = ensCache && tx.from && ensCache[tx.from];
const ensTo = ensCache && tx.to && ensCache[tx.to]; const ensTo = ensCache && tx.to && ensCache[tx.to];
const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction;
return ( return (
<div className="grid grid-cols-12 gap-x-1 items-baseline text-sm border-t border-gray-200 hover:bg-gray-100 px-2 py-3"> <div
className={`grid grid-cols-12 gap-x-1 items-baseline text-sm border-t border-gray-200 hover:bg-gray-100 ${
flash ? "bg-yellow-100" : ""
} px-2 py-3`}
>
<div className="col-span-2 flex space-x-1 items-baseline"> <div className="col-span-2 flex space-x-1 items-baseline">
{tx.status === 0 && ( {tx.status === 0 && (
<span className="text-red-600" title="Transaction reverted"> <span className="text-red-600" title="Transaction reverted">
@ -63,15 +72,25 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
<span className="col-span-2 flex justify-between items-baseline space-x-2 pr-2"> <span className="col-span-2 flex justify-between items-baseline space-x-2 pr-2">
<span className="truncate" title={tx.from}> <span className="truncate" title={tx.from}>
{tx.from && ( {tx.from && (
<AddressOrENSName <div className="flex items-baseline space-x-1">
address={tx.from} {tx.miner && tx.miner === tx.from && (
ensName={ensFrom} <span className="text-yellow-400" title="Miner address">
selectedAddress={selectedAddress} <FontAwesomeIcon icon={faCoins} size="1x" />
/> </span>
)}
<AddressOrENSName
address={tx.from}
ensName={ensFrom}
selectedAddress={selectedAddress}
/>
</div>
)} )}
</span> </span>
<span> <span>
<TransactionDirection direction={direction} /> <TransactionDirection
direction={direction}
flags={tx.internalMinerInteraction ? Flags.MINER : undefined}
/>
</span> </span>
</span> </span>
<span className="col-span-2 truncate" title={tx.to}> <span className="col-span-2 truncate" title={tx.to}>

View File

@ -1,12 +1,14 @@
import { BigNumber } from "ethers"; import { ethers, BigNumber } from "ethers";
export type ProcessedTransaction = { export type ProcessedTransaction = {
blockNumber: number; blockNumber: number;
timestamp: number; timestamp: number;
miner?: string;
idx: number; idx: number;
hash: string; hash: string;
from?: string; from?: string;
to?: string; to?: string;
internalMinerInteraction?: boolean;
value: BigNumber; value: BigNumber;
fee: BigNumber; fee: BigNumber;
gasPrice: BigNumber; gasPrice: BigNumber;
@ -23,3 +25,54 @@ export type TransactionChunk = {
export type ENSReverseCache = { export type ENSReverseCache = {
[address: string]: string; [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;
};