Merge branch 'feature/swr-everything-3' into develop
This commit is contained in:
commit
0acac32fc9
|
@ -1,17 +1,16 @@
|
|||
import React, { useState, useEffect, useMemo, useContext } from "react";
|
||||
import { Contract } from "@ethersproject/contracts";
|
||||
import React, { useMemo, useContext } from "react";
|
||||
import { commify, formatUnits } from "@ethersproject/units";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faGasPump } from "@fortawesome/free-solid-svg-icons/faGasPump";
|
||||
import AggregatorV3Interface from "@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { formatValue } from "./components/formatter";
|
||||
import { useLatestBlockHeader } from "./useLatestBlock";
|
||||
import { useChainInfo } from "./useChainInfo";
|
||||
import { useETHUSDRawOracle, useFastGasRawOracle } from "./usePriceOracle";
|
||||
|
||||
// TODO: encapsulate this magic number
|
||||
const ETH_FEED_DECIMALS = 8;
|
||||
|
||||
// TODO: reduce duplication with useETHUSDOracle
|
||||
const PriceBox: React.FC = () => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const {
|
||||
|
@ -22,37 +21,8 @@ const PriceBox: React.FC = () => {
|
|||
const maybeOutdated: boolean =
|
||||
latestBlock !== undefined &&
|
||||
Date.now() / 1000 - latestBlock.timestamp > 3600;
|
||||
const ethFeed = useMemo(
|
||||
() =>
|
||||
provider &&
|
||||
new Contract("eth-usd.data.eth", AggregatorV3Interface, provider),
|
||||
[provider]
|
||||
);
|
||||
const gasFeed = useMemo(
|
||||
() =>
|
||||
provider &&
|
||||
new Contract("fast-gas-gwei.data.eth", AggregatorV3Interface, provider),
|
||||
[provider]
|
||||
);
|
||||
|
||||
const [latestPriceData, setLatestPriceData] = useState<any>();
|
||||
const [latestGasData, setLatestGasData] = useState<any>();
|
||||
useEffect(() => {
|
||||
if (!ethFeed || !gasFeed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const readData = async () => {
|
||||
const [priceData, gasData] = await Promise.all([
|
||||
ethFeed.latestRoundData(),
|
||||
await gasFeed.latestRoundData(),
|
||||
]);
|
||||
setLatestPriceData(priceData);
|
||||
setLatestGasData(gasData);
|
||||
};
|
||||
readData();
|
||||
}, [ethFeed, gasFeed]);
|
||||
|
||||
const latestPriceData = useETHUSDRawOracle(provider, "latest");
|
||||
const [latestPrice, latestPriceTimestamp] = useMemo(() => {
|
||||
if (!latestPriceData) {
|
||||
return [undefined, undefined];
|
||||
|
@ -65,6 +35,7 @@ const PriceBox: React.FC = () => {
|
|||
return [formattedPrice, timestamp];
|
||||
}, [latestPriceData]);
|
||||
|
||||
const latestGasData = useFastGasRawOracle(provider, "latest");
|
||||
const [latestGasPrice, latestGasPriceTimestamp] = useMemo(() => {
|
||||
if (!latestGasData) {
|
||||
return [undefined, undefined];
|
||||
|
|
|
@ -6,24 +6,21 @@ import TransactionAddress from "./components/TransactionAddress";
|
|||
import ValueHighlighter from "./components/ValueHighlighter";
|
||||
import FormattedBalance from "./components/FormattedBalance";
|
||||
import USDAmount from "./components/USDAmount";
|
||||
import { AddressContext, TokenMeta, TokenTransfer } from "./types";
|
||||
import { RuntimeContext } from "./useRuntime";
|
||||
import { useBlockNumberContext } from "./useBlockTagContext";
|
||||
import { useTokenMetadata } from "./useErigonHooks";
|
||||
import { useTokenUSDOracle } from "./usePriceOracle";
|
||||
import { AddressContext, TokenTransfer } from "./types";
|
||||
|
||||
type TokenTransferItemProps = {
|
||||
t: TokenTransfer;
|
||||
tokenMeta?: TokenMeta | null | undefined;
|
||||
};
|
||||
|
||||
// TODO: handle partial
|
||||
const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
||||
t,
|
||||
tokenMeta,
|
||||
}) => {
|
||||
const TokenTransferItem: React.FC<TokenTransferItemProps> = ({ t }) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const blockNumber = useBlockNumberContext();
|
||||
const [quote, decimals] = useTokenUSDOracle(provider, blockNumber, t.token);
|
||||
const tokenMeta = useTokenMetadata(provider, t.token);
|
||||
|
||||
return (
|
||||
<div className="flex items-baseline space-x-2 px-2 py-1 truncate hover:bg-gray-100">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext } from "react";
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { useParams, Route, Routes } from "react-router-dom";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import StandardFrame from "./StandardFrame";
|
||||
|
@ -25,6 +25,12 @@ const Transaction: React.FC = () => {
|
|||
const txData = useTxData(provider, txHash);
|
||||
const selectionCtx = useSelection();
|
||||
|
||||
useEffect(() => {
|
||||
if (txData) {
|
||||
document.title = `Transaction ${txData.transactionHash} | Otterscan`;
|
||||
}
|
||||
}, [txData]);
|
||||
|
||||
return (
|
||||
<SelectedTransactionContext.Provider value={txData}>
|
||||
<BlockNumberContext.Provider value={txData?.confirmedData?.blockNumber}>
|
||||
|
@ -46,8 +52,7 @@ const Transaction: React.FC = () => {
|
|||
{txData.confirmedData?.blockNumber !== undefined && (
|
||||
<NavTab href="logs">
|
||||
Logs
|
||||
{txData &&
|
||||
` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||
{` (${txData.confirmedData?.logs?.length ?? 0})`}
|
||||
</NavTab>
|
||||
)}
|
||||
<NavTab href="trace">Trace</NavTab>
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { BaseProvider } from "@ethersproject/providers";
|
||||
import { Contract } from "@ethersproject/contracts";
|
||||
import { Interface } from "@ethersproject/abi";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { IAddressResolver } from "./address-resolver";
|
||||
import erc20 from "../../erc20.json";
|
||||
import { TokenMeta } from "../../types";
|
||||
|
||||
const erc20Interface = new Interface(erc20);
|
||||
const ERC20_PROTOTYPE = new Contract(AddressZero, erc20);
|
||||
|
||||
export class ERCTokenResolver implements IAddressResolver<TokenMeta> {
|
||||
async resolveAddress(
|
||||
provider: BaseProvider,
|
||||
address: string
|
||||
): Promise<TokenMeta | undefined> {
|
||||
const erc20Contract = new Contract(address, erc20Interface, provider);
|
||||
const erc20Contract = ERC20_PROTOTYPE.connect(provider).attach(address);
|
||||
try {
|
||||
const name = (await erc20Contract.name()) as string;
|
||||
if (!name.trim()) {
|
||||
|
|
|
@ -12,6 +12,11 @@ const UNISWAP_V1_FACTORY_ABI = [
|
|||
|
||||
const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
|
||||
|
||||
const UNISWAP_V1_FACTORY_PROTOTYPE = new Contract(
|
||||
UNISWAP_V1_FACTORY,
|
||||
UNISWAP_V1_FACTORY_ABI
|
||||
);
|
||||
|
||||
export type UniswapV1TokenMeta = {
|
||||
address: ChecksummedAddress;
|
||||
} & TokenMeta;
|
||||
|
@ -28,11 +33,7 @@ export class UniswapV1Resolver implements IAddressResolver<UniswapV1PairMeta> {
|
|||
provider: BaseProvider,
|
||||
address: string
|
||||
): Promise<UniswapV1PairMeta | undefined> {
|
||||
const factoryContract = new Contract(
|
||||
UNISWAP_V1_FACTORY,
|
||||
UNISWAP_V1_FACTORY_ABI,
|
||||
provider
|
||||
);
|
||||
const factoryContract = UNISWAP_V1_FACTORY_PROTOTYPE.connect(provider);
|
||||
|
||||
try {
|
||||
// First, probe the getToken() function; if it responds with an UniswapV1 exchange
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BaseProvider } from "@ethersproject/providers";
|
||||
import { Contract } from "@ethersproject/contracts";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { IAddressResolver } from "./address-resolver";
|
||||
import { ChecksummedAddress, TokenMeta } from "../../types";
|
||||
import { ERCTokenResolver } from "./ERCTokenResolver";
|
||||
|
@ -16,6 +17,16 @@ const UNISWAP_V2_PAIR_ABI = [
|
|||
"function token1() external view returns (address)",
|
||||
];
|
||||
|
||||
const UNISWAP_V2_FACTORY_PROTOTYPE = new Contract(
|
||||
UNISWAP_V2_FACTORY,
|
||||
UNISWAP_V2_FACTORY_ABI
|
||||
);
|
||||
|
||||
const UNISWAP_V2_PAIR_PROTOTYPE = new Contract(
|
||||
AddressZero,
|
||||
UNISWAP_V2_PAIR_ABI
|
||||
);
|
||||
|
||||
export type UniswapV2TokenMeta = {
|
||||
address: ChecksummedAddress;
|
||||
} & TokenMeta;
|
||||
|
@ -33,12 +44,9 @@ export class UniswapV2Resolver implements IAddressResolver<UniswapV2PairMeta> {
|
|||
provider: BaseProvider,
|
||||
address: string
|
||||
): Promise<UniswapV2PairMeta | undefined> {
|
||||
const pairContract = new Contract(address, UNISWAP_V2_PAIR_ABI, provider);
|
||||
const factoryContract = new Contract(
|
||||
UNISWAP_V2_FACTORY,
|
||||
UNISWAP_V2_FACTORY_ABI,
|
||||
provider
|
||||
);
|
||||
const pairContract =
|
||||
UNISWAP_V2_PAIR_PROTOTYPE.connect(provider).attach(address);
|
||||
const factoryContract = UNISWAP_V2_FACTORY_PROTOTYPE.connect(provider);
|
||||
|
||||
try {
|
||||
// First, probe the factory() function; if it responds with UniswapV2 factory
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BaseProvider } from "@ethersproject/providers";
|
||||
import { Contract } from "@ethersproject/contracts";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { IAddressResolver } from "./address-resolver";
|
||||
import { ChecksummedAddress, TokenMeta } from "../../types";
|
||||
import { ERCTokenResolver } from "./ERCTokenResolver";
|
||||
|
@ -17,6 +18,16 @@ const UNISWAP_V3_PAIR_ABI = [
|
|||
"function fee() external view returns (uint24)",
|
||||
];
|
||||
|
||||
const UNISWAP_V3_FACTORY_PROTOTYPE = new Contract(
|
||||
UNISWAP_V3_FACTORY,
|
||||
UNISWAP_V3_FACTORY_ABI
|
||||
);
|
||||
|
||||
const UNISWAP_V3_PAIR_PROTOTYPE = new Contract(
|
||||
AddressZero,
|
||||
UNISWAP_V3_PAIR_ABI
|
||||
);
|
||||
|
||||
export type UniswapV3TokenMeta = {
|
||||
address: ChecksummedAddress;
|
||||
} & TokenMeta;
|
||||
|
@ -35,12 +46,9 @@ export class UniswapV3Resolver implements IAddressResolver<UniswapV3PairMeta> {
|
|||
provider: BaseProvider,
|
||||
address: string
|
||||
): Promise<UniswapV3PairMeta | undefined> {
|
||||
const poolContract = new Contract(address, UNISWAP_V3_PAIR_ABI, provider);
|
||||
const factoryContract = new Contract(
|
||||
UNISWAP_V3_FACTORY,
|
||||
UNISWAP_V3_FACTORY_ABI,
|
||||
provider
|
||||
);
|
||||
const poolContract =
|
||||
UNISWAP_V3_PAIR_PROTOTYPE.connect(provider).attach(address);
|
||||
const factoryContract = UNISWAP_V3_FACTORY_PROTOTYPE.connect(provider);
|
||||
|
||||
try {
|
||||
// First, probe the factory() function; if it responds with UniswapV2 factory
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { formatEther } from "@ethersproject/units";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight";
|
||||
import AddressHighlighter from "./AddressHighlighter";
|
||||
import DecoratedAddressLink from "./DecoratedAddressLink";
|
||||
import TransactionAddress from "./TransactionAddress";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useBlockDataFromTransaction } from "../useErigonHooks";
|
||||
import { useChainInfo } from "../useChainInfo";
|
||||
import { TransactionData, InternalOperation } from "../types";
|
||||
|
||||
|
@ -17,12 +19,12 @@ const InternalSelfDestruct: React.FC<InternalSelfDestructProps> = ({
|
|||
txData,
|
||||
internalOp,
|
||||
}) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const block = useBlockDataFromTransaction(provider, txData);
|
||||
const {
|
||||
nativeCurrency: { symbol },
|
||||
} = useChainInfo();
|
||||
const toMiner =
|
||||
txData.confirmedData?.miner !== undefined &&
|
||||
internalOp.to === txData.confirmedData.miner;
|
||||
const toMiner = block?.miner !== undefined && internalOp.to === block.miner;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -8,7 +8,7 @@ import AddressHighlighter from "./AddressHighlighter";
|
|||
import DecoratedAddressLink from "./DecoratedAddressLink";
|
||||
import USDAmount from "./USDAmount";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useHasCode } from "../useErigonHooks";
|
||||
import { useBlockDataFromTransaction, useHasCode } from "../useErigonHooks";
|
||||
import { useChainInfo } from "../useChainInfo";
|
||||
import { useETHUSDOracle } from "../usePriceOracle";
|
||||
import { TransactionData, InternalOperation } from "../types";
|
||||
|
@ -22,17 +22,16 @@ const InternalTransfer: React.FC<InternalTransferProps> = ({
|
|||
txData,
|
||||
internalOp,
|
||||
}) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const block = useBlockDataFromTransaction(provider, txData);
|
||||
|
||||
const {
|
||||
nativeCurrency: { symbol, decimals },
|
||||
} = useChainInfo();
|
||||
const fromMiner =
|
||||
txData.confirmedData?.miner !== undefined &&
|
||||
internalOp.from === txData.confirmedData.miner;
|
||||
const toMiner =
|
||||
txData.confirmedData?.miner !== undefined &&
|
||||
internalOp.to === txData.confirmedData.miner;
|
||||
block?.miner !== undefined && internalOp.from === block.miner;
|
||||
const toMiner = block?.miner !== undefined && internalOp.to === block.miner;
|
||||
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const blockETHUSDPrice = useETHUSDOracle(
|
||||
provider,
|
||||
txData.confirmedData?.blockNumber
|
||||
|
|
|
@ -4,7 +4,7 @@ import DecoratedAddressLink from "./DecoratedAddressLink";
|
|||
import { useSelectedTransaction } from "../useSelectedTransaction";
|
||||
import { useBlockNumberContext } from "../useBlockTagContext";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useHasCode } from "../useErigonHooks";
|
||||
import { useBlockDataFromTransaction, useHasCode } from "../useErigonHooks";
|
||||
import { AddressContext, ChecksummedAddress } from "../types";
|
||||
|
||||
type TransactionAddressProps = {
|
||||
|
@ -23,6 +23,8 @@ const TransactionAddress: React.FC<TransactionAddressProps> = ({
|
|||
const creation = address === txData?.confirmedData?.createdContractAddress;
|
||||
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const block = useBlockDataFromTransaction(provider, txData);
|
||||
|
||||
const blockNumber = useBlockNumberContext();
|
||||
const toHasCode = useHasCode(
|
||||
provider,
|
||||
|
@ -39,7 +41,7 @@ const TransactionAddress: React.FC<TransactionAddressProps> = ({
|
|||
<DecoratedAddressLink
|
||||
address={address}
|
||||
addressCtx={addressCtx}
|
||||
miner={address === txData?.confirmedData?.miner}
|
||||
miner={address === block?.miner}
|
||||
txFrom={address === txData?.from}
|
||||
txTo={address === txData?.to || creation}
|
||||
creation={creation}
|
||||
|
|
|
@ -11,6 +11,7 @@ export enum Direction {
|
|||
}
|
||||
|
||||
export enum Flags {
|
||||
// Means the transaction internal sends ETH to the miner, e.g. flashbots
|
||||
MINER,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import { JsonRpcProvider } from "@ethersproject/providers";
|
||||
import { getAddress } from "@ethersproject/address";
|
||||
import { InternalOperation } from "./types";
|
||||
|
||||
export const getInternalOperations = async (
|
||||
provider: JsonRpcProvider,
|
||||
txHash: string
|
||||
) => {
|
||||
const rawTransfers = await provider.send("ots_getInternalOperations", [
|
||||
txHash,
|
||||
]);
|
||||
|
||||
const _transfers: InternalOperation[] = [];
|
||||
for (const t of rawTransfers) {
|
||||
_transfers.push({
|
||||
type: t.type,
|
||||
from: getAddress(t.from),
|
||||
to: getAddress(t.to),
|
||||
value: t.value,
|
||||
});
|
||||
}
|
||||
return _transfers;
|
||||
};
|
|
@ -16,7 +16,7 @@ import TransactionItemFiatFee from "./TransactionItemFiatFee";
|
|||
import { ProcessedTransaction } from "../types";
|
||||
import { FeeDisplay } from "./useFeeToggler";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useHasCode } from "../useErigonHooks";
|
||||
import { useHasCode, useSendsToMiner } from "../useErigonHooks";
|
||||
import { formatValue } from "../components/formatter";
|
||||
|
||||
type TransactionItemProps = {
|
||||
|
@ -36,6 +36,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
tx.to ?? undefined,
|
||||
tx.blockNumber - 1
|
||||
);
|
||||
const [sendsToMiner] = useSendsToMiner(provider, tx.hash, tx.miner);
|
||||
|
||||
let direction: Direction | undefined;
|
||||
if (selectedAddress) {
|
||||
|
@ -53,7 +54,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction;
|
||||
const flash = tx.gasPrice.isZero() && sendsToMiner;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -91,7 +92,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
|||
<span>
|
||||
<TransactionDirection
|
||||
direction={direction}
|
||||
flags={tx.internalMinerInteraction ? Flags.MINER : undefined}
|
||||
flags={sendsToMiner ? Flags.MINER : undefined}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useMemo, useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheckCircle } from "@fortawesome/free-solid-svg-icons/faCheckCircle";
|
||||
|
@ -43,7 +43,12 @@ import {
|
|||
useTransactionDescription as useSourcifyTransactionDescription,
|
||||
} from "../sourcify/useSourcify";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useInternalOperations, useTransactionError } from "../useErigonHooks";
|
||||
import {
|
||||
useBlockDataFromTransaction,
|
||||
useSendsToMiner,
|
||||
useTokenTransfers,
|
||||
useTransactionError,
|
||||
} from "../useErigonHooks";
|
||||
import { useChainInfo } from "../useChainInfo";
|
||||
import { useETHUSDOracle } from "../usePriceOracle";
|
||||
|
||||
|
@ -53,10 +58,10 @@ type DetailsProps = {
|
|||
|
||||
const Details: React.FC<DetailsProps> = ({ txData }) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const block = useBlockDataFromTransaction(provider, txData);
|
||||
|
||||
const hasEIP1559 =
|
||||
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
|
||||
txData.confirmedData?.blockBaseFeePerGas !== null;
|
||||
block?.baseFeePerGas !== undefined && block?.baseFeePerGas !== null;
|
||||
|
||||
const fourBytes =
|
||||
txData.to !== null ? extract4Bytes(txData.data) ?? "0x" : "0x";
|
||||
|
@ -67,19 +72,13 @@ const Details: React.FC<DetailsProps> = ({ txData }) => {
|
|||
txData.value
|
||||
);
|
||||
|
||||
const internalOps = useInternalOperations(provider, txData);
|
||||
const sendsEthToMiner = useMemo(() => {
|
||||
if (!txData || !internalOps) {
|
||||
return false;
|
||||
}
|
||||
const [sendsEthToMiner, internalOps] = useSendsToMiner(
|
||||
provider,
|
||||
txData.confirmedData ? txData.transactionHash : undefined,
|
||||
block?.miner
|
||||
);
|
||||
|
||||
for (const t of internalOps) {
|
||||
if (t.to === txData.confirmedData?.miner) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [txData, internalOps]);
|
||||
const tokenTransfers = useTokenTransfers(txData);
|
||||
|
||||
const metadata = useSourcifyMetadata(txData?.to, provider?.network.chainId);
|
||||
|
||||
|
@ -224,22 +223,24 @@ const Details: React.FC<DetailsProps> = ({ txData }) => {
|
|||
confirmations={txData.confirmedData.confirmations}
|
||||
/>
|
||||
</div>
|
||||
{block && (
|
||||
<div className="flex space-x-2 items-baseline pl-3">
|
||||
<RelativePosition
|
||||
pos={txData.confirmedData.transactionIndex}
|
||||
total={txData.confirmedData.blockTransactionCount - 1}
|
||||
total={block.transactionCount - 1}
|
||||
/>
|
||||
<PercentagePosition
|
||||
perc={
|
||||
txData.confirmedData.transactionIndex /
|
||||
(txData.confirmedData.blockTransactionCount - 1)
|
||||
(block.transactionCount - 1)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</InfoRow>
|
||||
<InfoRow title="Timestamp">
|
||||
<Timestamp value={txData.confirmedData.timestamp} />
|
||||
{block && <Timestamp value={block.timestamp} />}
|
||||
</InfoRow>
|
||||
</>
|
||||
)}
|
||||
|
@ -290,14 +291,10 @@ const Details: React.FC<DetailsProps> = ({ txData }) => {
|
|||
<MethodName data={txData.data} />
|
||||
</InfoRow>
|
||||
)}
|
||||
{txData.tokenTransfers.length > 0 && (
|
||||
<InfoRow title={`Tokens Transferred (${txData.tokenTransfers.length})`}>
|
||||
{txData.tokenTransfers.map((t, i) => (
|
||||
<TokenTransferItem
|
||||
key={i}
|
||||
t={t}
|
||||
tokenMeta={txData.tokenMetas[t.token]}
|
||||
/>
|
||||
{tokenTransfers && tokenTransfers.length > 0 && (
|
||||
<InfoRow title={`Tokens Transferred (${tokenTransfers.length})`}>
|
||||
{tokenTransfers.map((t, i) => (
|
||||
<TokenTransferItem key={i} t={t} />
|
||||
))}
|
||||
</InfoRow>
|
||||
)}
|
||||
|
@ -372,18 +369,10 @@ const Details: React.FC<DetailsProps> = ({ txData }) => {
|
|||
</div>
|
||||
</InfoRow>
|
||||
)}
|
||||
{txData.confirmedData && hasEIP1559 && (
|
||||
{block && hasEIP1559 && (
|
||||
<InfoRow title="Block Base Fee">
|
||||
<FormattedBalance
|
||||
value={txData.confirmedData.blockBaseFeePerGas!}
|
||||
decimals={9}
|
||||
/>{" "}
|
||||
Gwei (
|
||||
<FormattedBalance
|
||||
value={txData.confirmedData.blockBaseFeePerGas!}
|
||||
decimals={0}
|
||||
/>{" "}
|
||||
wei)
|
||||
<FormattedBalance value={block.baseFeePerGas!} decimals={9} /> Gwei (
|
||||
<FormattedBalance value={block.baseFeePerGas!} decimals={0} /> wei)
|
||||
</InfoRow>
|
||||
)}
|
||||
{txData.confirmedData && (
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { BigNumber } from "@ethersproject/bignumber";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
||||
import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
|
||||
import FormattedBalance from "../components/FormattedBalance";
|
||||
import PercentageGauge from "../components/PercentageGauge";
|
||||
import { TransactionData } from "../types";
|
||||
import { RuntimeContext } from "../useRuntime";
|
||||
import { useBlockDataFromTransaction } from "../useErigonHooks";
|
||||
import { useChainInfo } from "../useChainInfo";
|
||||
import { TransactionData } from "../types";
|
||||
|
||||
type RewardSplitProps = {
|
||||
txData: TransactionData;
|
||||
};
|
||||
|
||||
const RewardSplit: React.FC<RewardSplitProps> = ({ txData }) => {
|
||||
const { provider } = useContext(RuntimeContext);
|
||||
const block = useBlockDataFromTransaction(provider, txData);
|
||||
|
||||
const {
|
||||
nativeCurrency: { symbol },
|
||||
} = useChainInfo();
|
||||
const paidFees = txData.gasPrice.mul(txData.confirmedData!.gasUsed);
|
||||
const burntFees = txData.confirmedData!.blockBaseFeePerGas!.mul(
|
||||
txData.confirmedData!.gasUsed
|
||||
);
|
||||
const burntFees = block
|
||||
? block.baseFeePerGas!.mul(txData.confirmedData!.gasUsed)
|
||||
: BigNumber.from(0);
|
||||
|
||||
const minerReward = paidFees.sub(burntFees);
|
||||
const burntPerc =
|
||||
|
|
|
@ -18,7 +18,6 @@ export type ProcessedTransaction = {
|
|||
from?: string;
|
||||
to: string | null;
|
||||
createdContractAddress?: string;
|
||||
internalMinerInteraction?: boolean;
|
||||
value: BigNumber;
|
||||
fee: BigNumber;
|
||||
gasPrice: BigNumber;
|
||||
|
@ -37,8 +36,6 @@ export type TransactionData = {
|
|||
from: string;
|
||||
to?: string;
|
||||
value: BigNumber;
|
||||
tokenTransfers: TokenTransfer[];
|
||||
tokenMetas: TokenMetas;
|
||||
type: number;
|
||||
maxFeePerGas?: BigNumber | undefined;
|
||||
maxPriorityFeePerGas?: BigNumber | undefined;
|
||||
|
@ -53,11 +50,7 @@ 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;
|
||||
|
|
|
@ -53,6 +53,7 @@ const fourBytesFetcher =
|
|||
const fourBytes = key.slice(2);
|
||||
const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes);
|
||||
|
||||
try {
|
||||
const res = await fetch(signatureURL);
|
||||
if (!res.ok) {
|
||||
console.warn(`Signature does not exist in 4bytes DB: ${fourBytes}`);
|
||||
|
@ -70,6 +71,11 @@ const fourBytesFetcher =
|
|||
signature: sig,
|
||||
};
|
||||
return entry;
|
||||
} catch (err) {
|
||||
// Network error or something wrong with URL config;
|
||||
// silence and don't try it again
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import {
|
||||
Block,
|
||||
BlockWithTransactions,
|
||||
|
@ -10,17 +10,17 @@ import { Contract } from "@ethersproject/contracts";
|
|||
import { defaultAbiCoder } from "@ethersproject/abi";
|
||||
import { BigNumber } from "@ethersproject/bignumber";
|
||||
import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import useSWR from "swr";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import { getInternalOperations } from "./nodeFunctions";
|
||||
import {
|
||||
TokenMetas,
|
||||
TokenTransfer,
|
||||
TransactionData,
|
||||
InternalOperation,
|
||||
ProcessedTransaction,
|
||||
OperationType,
|
||||
ChecksummedAddress,
|
||||
TokenMeta,
|
||||
} from "./types";
|
||||
import erc20 from "./erc20.json";
|
||||
|
||||
|
@ -141,27 +141,6 @@ export const useBlockTransactions = (
|
|||
.reverse();
|
||||
setTxs(rawTxs);
|
||||
setTotalTxs(result.fullblock.transactionCount);
|
||||
|
||||
const checkTouchMinerAddr = await Promise.all(
|
||||
rawTxs.map(async (res) => {
|
||||
const ops = await getInternalOperations(provider, res.hash);
|
||||
return (
|
||||
ops.findIndex(
|
||||
(op) =>
|
||||
op.type === OperationType.TRANSFER &&
|
||||
res.miner !== undefined &&
|
||||
res.miner === getAddress(op.to)
|
||||
) !== -1
|
||||
);
|
||||
})
|
||||
);
|
||||
const processedTxs = rawTxs.map(
|
||||
(r, i): ProcessedTransaction => ({
|
||||
...r,
|
||||
internalMinerInteraction: checkTouchMinerAddr[i],
|
||||
})
|
||||
);
|
||||
setTxs(processedTxs);
|
||||
};
|
||||
readBlock();
|
||||
}, [provider, blockNumber, pageNumber, pageSize]);
|
||||
|
@ -169,23 +148,40 @@ export const useBlockTransactions = (
|
|||
return [totalTxs, txs];
|
||||
};
|
||||
|
||||
const blockDataFetcher = async (
|
||||
provider: JsonRpcProvider,
|
||||
blockNumberOrHash: string
|
||||
) => {
|
||||
return await readBlock(provider, blockNumberOrHash);
|
||||
};
|
||||
|
||||
// TODO: some callers may use only block headers?
|
||||
export const useBlockData = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
blockNumberOrHash: string
|
||||
blockNumberOrHash: string | undefined
|
||||
): ExtendedBlock | null | undefined => {
|
||||
const [block, setBlock] = useState<ExtendedBlock | null | undefined>();
|
||||
useEffect(() => {
|
||||
if (!provider) {
|
||||
const { data, error } = useSWRImmutable(
|
||||
provider !== undefined && blockNumberOrHash !== undefined
|
||||
? [provider, blockNumberOrHash]
|
||||
: null,
|
||||
blockDataFetcher
|
||||
);
|
||||
if (error) {
|
||||
return undefined;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const _readBlock = async () => {
|
||||
const extBlock = await readBlock(provider, blockNumberOrHash);
|
||||
setBlock(extBlock);
|
||||
};
|
||||
_readBlock();
|
||||
}, [provider, blockNumberOrHash]);
|
||||
|
||||
export const useBlockDataFromTransaction = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
txData: TransactionData | null | undefined
|
||||
): ExtendedBlock | null | undefined => {
|
||||
const block = useBlockData(
|
||||
provider,
|
||||
txData?.confirmedData
|
||||
? txData.confirmedData.blockNumber.toString()
|
||||
: undefined
|
||||
);
|
||||
return block;
|
||||
};
|
||||
|
||||
|
@ -211,66 +207,11 @@ export const useTxData = (
|
|||
return;
|
||||
}
|
||||
|
||||
let _block: ExtendedBlock | null | undefined;
|
||||
if (_response.blockNumber) {
|
||||
_block = await readBlock(provider, _response.blockNumber.toString());
|
||||
}
|
||||
|
||||
document.title = `Transaction ${_response.hash} | Otterscan`;
|
||||
|
||||
// Extract token transfers
|
||||
const tokenTransfers: TokenTransfer[] = [];
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract token meta
|
||||
const tokenMetas: TokenMetas = {};
|
||||
for (const t of tokenTransfers) {
|
||||
if (tokenMetas[t.token] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
const erc20Contract = new Contract(t.token, erc20, provider);
|
||||
try {
|
||||
const [name, symbol, decimals] = await Promise.all([
|
||||
erc20Contract.name(),
|
||||
erc20Contract.symbol(),
|
||||
erc20Contract.decimals(),
|
||||
]);
|
||||
tokenMetas[t.token] = {
|
||||
name,
|
||||
symbol,
|
||||
decimals,
|
||||
};
|
||||
} catch (err) {
|
||||
tokenMetas[t.token] = null;
|
||||
console.warn(
|
||||
`Couldn't get token ${t.token} metadata; ignoring`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setTxData({
|
||||
transactionHash: _response.hash,
|
||||
from: _response.from,
|
||||
to: _response.to,
|
||||
value: _response.value,
|
||||
tokenTransfers,
|
||||
tokenMetas,
|
||||
type: _response.type ?? 0,
|
||||
maxFeePerGas: _response.maxFeePerGas,
|
||||
maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
|
||||
|
@ -285,11 +226,7 @@ export const useTxData = (
|
|||
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,
|
||||
|
@ -308,33 +245,75 @@ export const useTxData = (
|
|||
return txData;
|
||||
};
|
||||
|
||||
export const useTokenTransfers = (
|
||||
txData: TransactionData
|
||||
): TokenTransfer[] | undefined => {
|
||||
const transfers = useMemo(() => {
|
||||
if (!txData.confirmedData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return txData.confirmedData.logs
|
||||
.filter((l) => l.topics.length === 3 && l.topics[0] === TRANSFER_TOPIC)
|
||||
.map((l) => ({
|
||||
token: l.address,
|
||||
from: getAddress(hexDataSlice(arrayify(l.topics[1]), 12)),
|
||||
to: getAddress(hexDataSlice(arrayify(l.topics[2]), 12)),
|
||||
value: BigNumber.from(l.data),
|
||||
}));
|
||||
}, [txData]);
|
||||
|
||||
return transfers;
|
||||
};
|
||||
|
||||
export const useInternalOperations = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
txData: TransactionData | undefined | null
|
||||
txHash: string | undefined
|
||||
): InternalOperation[] | undefined => {
|
||||
const [intTransfers, setIntTransfers] = useState<InternalOperation[]>();
|
||||
|
||||
useEffect(() => {
|
||||
const traceTransfers = async () => {
|
||||
if (!provider || !txData || !txData.confirmedData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _transfers = await getInternalOperations(
|
||||
provider,
|
||||
txData.transactionHash
|
||||
const { data, error } = useSWRImmutable(
|
||||
provider !== undefined && txHash !== undefined
|
||||
? ["ots_getInternalOperations", txHash]
|
||||
: null,
|
||||
providerFetcher(provider)
|
||||
);
|
||||
for (const t of _transfers) {
|
||||
t.from = provider.formatter.address(t.from);
|
||||
t.to = provider.formatter.address(t.to);
|
||||
t.value = provider.formatter.bigNumber(t.value);
|
||||
}
|
||||
setIntTransfers(_transfers);
|
||||
};
|
||||
traceTransfers();
|
||||
}, [provider, txData]);
|
||||
|
||||
return intTransfers;
|
||||
const _transfers = useMemo(() => {
|
||||
if (provider === undefined || error || data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const _t: InternalOperation[] = [];
|
||||
for (const t of data) {
|
||||
_t.push({
|
||||
type: t.type,
|
||||
from: provider.formatter.address(getAddress(t.from)),
|
||||
to: provider.formatter.address(getAddress(t.to)),
|
||||
value: provider.formatter.bigNumber(t.value),
|
||||
});
|
||||
}
|
||||
return _t;
|
||||
}, [provider, data]);
|
||||
return _transfers;
|
||||
};
|
||||
|
||||
export const useSendsToMiner = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
txHash: string | undefined,
|
||||
miner: string | undefined
|
||||
): [boolean, InternalOperation[]] | [undefined, undefined] => {
|
||||
const ops = useInternalOperations(provider, txHash);
|
||||
if (ops === undefined) {
|
||||
return [undefined, undefined];
|
||||
}
|
||||
|
||||
const send =
|
||||
ops.findIndex(
|
||||
(op) =>
|
||||
op.type === OperationType.TRANSFER &&
|
||||
miner !== undefined &&
|
||||
miner === getAddress(op.to)
|
||||
) !== -1;
|
||||
return [send, ops];
|
||||
};
|
||||
|
||||
export type TraceEntry = {
|
||||
|
@ -665,3 +644,61 @@ export const useHasCode = (
|
|||
}
|
||||
return data as boolean | undefined;
|
||||
};
|
||||
|
||||
const ERC20_PROTOTYPE = new Contract(AddressZero, erc20);
|
||||
|
||||
const tokenMetadataFetcher =
|
||||
(provider: JsonRpcProvider | undefined) =>
|
||||
async (
|
||||
_: "tokenmeta",
|
||||
address: ChecksummedAddress
|
||||
): Promise<TokenMeta | null> => {
|
||||
if (provider === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const erc20Contract = ERC20_PROTOTYPE.connect(provider).attach(address);
|
||||
try {
|
||||
const name = (await erc20Contract.name()) as string;
|
||||
if (!name.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [symbol, decimals] = (await Promise.all([
|
||||
erc20Contract.symbol(),
|
||||
erc20Contract.decimals(),
|
||||
])) as [string, number];
|
||||
|
||||
// Prevent faulty tokens with empty name/symbol
|
||||
if (!symbol.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
symbol,
|
||||
decimals,
|
||||
};
|
||||
} catch (err) {
|
||||
// Ignore on purpose; this indicates the probe failed and the address
|
||||
// is not a token
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const useTokenMetadata = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
address: ChecksummedAddress | undefined
|
||||
): TokenMeta | null | undefined => {
|
||||
const fetcher = tokenMetadataFetcher(provider);
|
||||
const { data, error } = useSWRImmutable(
|
||||
provider !== undefined && address !== undefined
|
||||
? ["tokenmeta", address]
|
||||
: null,
|
||||
fetcher
|
||||
);
|
||||
if (error) {
|
||||
return undefined;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { JsonRpcProvider, BlockTag } from "@ethersproject/providers";
|
||||
import { Contract } from "@ethersproject/contracts";
|
||||
import { BigNumber } from "@ethersproject/bignumber";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import AggregatorV3Interface from "@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json";
|
||||
import FeedRegistryInterface from "@chainlink/contracts/abi/v0.8/FeedRegistryInterface.json";
|
||||
import { Fetcher } from "swr";
|
||||
|
@ -26,11 +27,20 @@ const feedRegistryFetcherKey = (
|
|||
return [tokenAddress, blockTag];
|
||||
};
|
||||
|
||||
const FEED_REGISTRY_MAINNET_PROTOTYPE = new Contract(
|
||||
FEED_REGISTRY_MAINNET,
|
||||
FeedRegistryInterface
|
||||
);
|
||||
|
||||
const feedRegistryFetcher =
|
||||
(
|
||||
provider: JsonRpcProvider | undefined
|
||||
): Fetcher<FeedRegistryFetcherData, FeedRegistryFetcherKey> =>
|
||||
async (tokenAddress, blockTag) => {
|
||||
if (provider === undefined) {
|
||||
return [undefined, undefined];
|
||||
}
|
||||
|
||||
// It work works on ethereum mainnet and kovan, see:
|
||||
// https://docs.chain.link/docs/feed-registry/
|
||||
if (provider!.network.chainId !== 1) {
|
||||
|
@ -38,11 +48,7 @@ const feedRegistryFetcher =
|
|||
}
|
||||
|
||||
// Let SWR handle error
|
||||
const feedRegistry = new Contract(
|
||||
FEED_REGISTRY_MAINNET,
|
||||
FeedRegistryInterface,
|
||||
provider
|
||||
);
|
||||
const feedRegistry = FEED_REGISTRY_MAINNET_PROTOTYPE.connect(provider);
|
||||
const priceData = await feedRegistry.latestRoundData(tokenAddress, USD, {
|
||||
blockTag,
|
||||
});
|
||||
|
@ -76,23 +82,39 @@ const ethUSDFetcherKey = (blockTag: BlockTag | undefined) => {
|
|||
return ["ethusd", blockTag];
|
||||
};
|
||||
|
||||
const ETH_USD_FEED_PROTOTYPE = new Contract(AddressZero, AggregatorV3Interface);
|
||||
|
||||
const ethUSDFetcher =
|
||||
(
|
||||
provider: JsonRpcProvider | undefined
|
||||
): Fetcher<BigNumber | undefined, ["ethusd", BlockTag | undefined]> =>
|
||||
): Fetcher<any | undefined, ["ethusd", BlockTag | undefined]> =>
|
||||
async (_, blockTag) => {
|
||||
if (provider?.network.chainId !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
const c = new Contract("eth-usd.data.eth", AggregatorV3Interface, provider);
|
||||
|
||||
const c =
|
||||
ETH_USD_FEED_PROTOTYPE.connect(provider).attach("eth-usd.data.eth");
|
||||
const priceData = await c.latestRoundData({ blockTag });
|
||||
return BigNumber.from(priceData.answer);
|
||||
return priceData;
|
||||
};
|
||||
|
||||
export const useETHUSDOracle = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
blockTag: BlockTag | undefined
|
||||
): BigNumber | undefined => {
|
||||
const fetcher = ethUSDFetcher(provider);
|
||||
const { data, error } = useSWRImmutable(ethUSDFetcherKey(blockTag), fetcher);
|
||||
if (error) {
|
||||
return undefined;
|
||||
}
|
||||
return data !== undefined ? BigNumber.from(data.answer) : undefined;
|
||||
};
|
||||
|
||||
export const useETHUSDRawOracle = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
blockTag: BlockTag | undefined
|
||||
): any | undefined => {
|
||||
const fetcher = ethUSDFetcher(provider);
|
||||
const { data, error } = useSWRImmutable(ethUSDFetcherKey(blockTag), fetcher);
|
||||
if (error) {
|
||||
|
@ -100,3 +122,42 @@ export const useETHUSDOracle = (
|
|||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const fastGasFetcherKey = (blockTag: BlockTag | undefined) => {
|
||||
if (blockTag === undefined) {
|
||||
return null;
|
||||
}
|
||||
return ["gasgwei", blockTag];
|
||||
};
|
||||
|
||||
const FAST_GAS_FEED_PROTOTYPE = new Contract(
|
||||
AddressZero,
|
||||
AggregatorV3Interface
|
||||
);
|
||||
|
||||
const fastGasFetcher =
|
||||
(
|
||||
provider: JsonRpcProvider | undefined
|
||||
): Fetcher<any | undefined, ["gasgwei", BlockTag | undefined]> =>
|
||||
async (_, blockTag) => {
|
||||
if (provider?.network.chainId !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
const c = FAST_GAS_FEED_PROTOTYPE.connect(provider).attach(
|
||||
"fast-gas-gwei.data.eth"
|
||||
);
|
||||
const priceData = await c.latestRoundData({ blockTag });
|
||||
return priceData;
|
||||
};
|
||||
|
||||
export const useFastGasRawOracle = (
|
||||
provider: JsonRpcProvider | undefined,
|
||||
blockTag: BlockTag | undefined
|
||||
): any | undefined => {
|
||||
const fetcher = fastGasFetcher(provider);
|
||||
const { data, error } = useSWRImmutable(fastGasFetcherKey(blockTag), fetcher);
|
||||
if (error) {
|
||||
return undefined;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue