Merge branch 'feature/flash-transactions' into develop
This commit is contained in:
commit
646d345215
|
@ -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]);
|
||||||
|
|
|
@ -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>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -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);
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
55
src/types.ts
55
src/types.ts
|
@ -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;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue