Merge branch 'feature/usd-price-on-search-results' into develop

This commit is contained in:
Willian Mitsuda 2021-08-31 18:07:25 -03:00
commit 47212a1db0
7 changed files with 84 additions and 16 deletions

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useMemo, useContext } from "react"; import React, { useState, useEffect, useMemo, useContext } from "react";
import { useParams, useLocation, useHistory } from "react-router-dom"; import { useParams, useLocation, useHistory } from "react-router-dom";
import { BlockTag } from "@ethersproject/abstract-provider";
import { getAddress, isAddress } from "@ethersproject/address"; import { getAddress, isAddress } from "@ethersproject/address";
import queryString from "query-string"; import queryString from "query-string";
import Blockies from "react-blockies"; import Blockies from "react-blockies";
@ -16,6 +17,7 @@ import { RuntimeContext } from "./useRuntime";
import { useENSCache } from "./useReverseCache"; import { useENSCache } from "./useReverseCache";
import { useFeeToggler } from "./search/useFeeToggler"; import { useFeeToggler } from "./search/useFeeToggler";
import { SelectionContext, useSelection } from "./useSelection"; import { SelectionContext, useSelection } from "./useSelection";
import { useMultipleETHUSDOracle } from "./usePriceOracle";
type BlockParams = { type BlockParams = {
addressOrName: string; addressOrName: string;
@ -150,6 +152,14 @@ const AddressTransactions: React.FC = () => {
const page = useMemo(() => controller?.getPage(), [controller]); const page = useMemo(() => controller?.getPage(), [controller]);
const reverseCache = useENSCache(provider, page); const reverseCache = useENSCache(provider, page);
const blockTags: BlockTag[] = useMemo(() => {
if (!page) {
return [];
}
return page.map((p) => p.blockNumber);
}, [page]);
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
document.title = `Address ${params.addressOrName} | Otterscan`; document.title = `Address ${params.addressOrName} | Otterscan`;
const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const [feeDisplay, feeDisplayToggler] = useFeeToggler();
@ -215,6 +225,7 @@ const AddressTransactions: React.FC = () => {
ensCache={reverseCache} ensCache={reverseCache}
selectedAddress={checksummedAddress} selectedAddress={checksummedAddress}
feeDisplay={feeDisplay} feeDisplay={feeDisplay}
priceMap={priceMap}
/> />
))} ))}
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">

View File

@ -47,6 +47,7 @@ const BlockTransactions: React.FC = () => {
<StandardFrame> <StandardFrame>
<BlockTransactionHeader blockTag={blockNumber.toNumber()} /> <BlockTransactionHeader blockTag={blockNumber.toNumber()} />
<BlockTransactionResults <BlockTransactionResults
blockTag={blockNumber.toNumber()}
page={txs} page={txs}
total={totalTxs ?? 0} total={totalTxs ?? 0}
pageNumber={pageNumber} pageNumber={pageNumber}

View File

@ -1,4 +1,5 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import { BlockTag } from "@ethersproject/abstract-provider";
import ContentFrame from "../ContentFrame"; import ContentFrame from "../ContentFrame";
import PageControl from "../search/PageControl"; import PageControl from "../search/PageControl";
import ResultHeader from "../search/ResultHeader"; import ResultHeader from "../search/ResultHeader";
@ -10,14 +11,17 @@ import { SelectionContext, useSelection } from "../useSelection";
import { useENSCache } from "../useReverseCache"; import { useENSCache } from "../useReverseCache";
import { ProcessedTransaction } from "../types"; import { ProcessedTransaction } from "../types";
import { PAGE_SIZE } from "../params"; import { PAGE_SIZE } from "../params";
import { useMultipleETHUSDOracle } from "../usePriceOracle";
type BlockTransactionResultsProps = { type BlockTransactionResultsProps = {
blockTag: BlockTag;
page?: ProcessedTransaction[]; page?: ProcessedTransaction[];
total: number; total: number;
pageNumber: number; pageNumber: number;
}; };
const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
blockTag,
page, page,
total, total,
pageNumber, pageNumber,
@ -26,6 +30,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const [feeDisplay, feeDisplayToggler] = useFeeToggler();
const { provider } = useContext(RuntimeContext); const { provider } = useContext(RuntimeContext);
const reverseCache = useENSCache(provider, page); const reverseCache = useENSCache(provider, page);
const priceMap = useMultipleETHUSDOracle(provider, [blockTag]);
return ( return (
<ContentFrame> <ContentFrame>
@ -55,6 +60,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
tx={tx} tx={tx}
ensCache={reverseCache} ensCache={reverseCache}
feeDisplay={feeDisplay} feeDisplay={feeDisplay}
priceMap={priceMap}
/> />
))} ))}
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">

View File

@ -23,7 +23,9 @@ const ResultHeader: React.FC<ResultHeaderProps> = ({
className="text-link-blue hover:text-link-blue-hover" className="text-link-blue hover:text-link-blue-hover"
onClick={feeDisplayToggler} onClick={feeDisplayToggler}
> >
{feeDisplay === FeeDisplay.TX_FEE ? "Txn Fee" : "Gas Price"} {feeDisplay === FeeDisplay.TX_FEE && "Txn Fee"}
{feeDisplay === FeeDisplay.TX_FEE_USD && "Txn Fee (USD)"}
{feeDisplay === FeeDisplay.GAS_PRICE && "Gas Price"}
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,4 +1,6 @@
import React from "react"; import React from "react";
import { BlockTag } from "@ethersproject/abstract-provider";
import { BigNumber } from "@ethersproject/bignumber";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
import MethodName from "../components/MethodName"; import MethodName from "../components/MethodName";
@ -15,12 +17,14 @@ import TransactionValue from "../components/TransactionValue";
import { ENSReverseCache, ProcessedTransaction } from "../types"; import { ENSReverseCache, ProcessedTransaction } from "../types";
import { FeeDisplay } from "./useFeeToggler"; import { FeeDisplay } from "./useFeeToggler";
import { formatValue } from "../components/formatter"; import { formatValue } from "../components/formatter";
import ETH2USDValue from "../components/ETH2USDValue";
type TransactionItemProps = { type TransactionItemProps = {
tx: ProcessedTransaction; tx: ProcessedTransaction;
ensCache?: ENSReverseCache; ensCache?: ENSReverseCache;
selectedAddress?: string; selectedAddress?: string;
feeDisplay: FeeDisplay; feeDisplay: FeeDisplay;
priceMap: Record<BlockTag, BigNumber>;
}; };
const TransactionItem: React.FC<TransactionItemProps> = ({ const TransactionItem: React.FC<TransactionItemProps> = ({
@ -28,6 +32,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
ensCache, ensCache,
selectedAddress, selectedAddress,
feeDisplay, feeDisplay,
priceMap,
}) => { }) => {
let direction: Direction | undefined; let direction: Direction | undefined;
if (selectedAddress) { if (selectedAddress) {
@ -123,9 +128,17 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
<TransactionValue value={tx.value} /> <TransactionValue value={tx.value} />
</span> </span>
<span className="font-balance text-xs text-gray-500 truncate"> <span className="font-balance text-xs text-gray-500 truncate">
{feeDisplay === FeeDisplay.TX_FEE {feeDisplay === FeeDisplay.TX_FEE && formatValue(tx.fee, 18)}
? formatValue(tx.fee, 18) {feeDisplay === FeeDisplay.TX_FEE_USD &&
: formatValue(tx.gasPrice, 9)} (priceMap[tx.blockNumber] ? (
<ETH2USDValue
ethAmount={tx.fee}
eth2USDValue={priceMap[tx.blockNumber]}
/>
) : (
"N/A"
))}
{feeDisplay === FeeDisplay.GAS_PRICE && formatValue(tx.gasPrice, 9)}
</span> </span>
</div> </div>
); );

View File

@ -2,16 +2,16 @@ import { useState } from "react";
export enum FeeDisplay { export enum FeeDisplay {
TX_FEE, TX_FEE,
TX_FEE_USD,
GAS_PRICE, GAS_PRICE,
} }
export const useFeeToggler = (): [FeeDisplay, () => void] => { export const useFeeToggler = (): [FeeDisplay, () => void] => {
const [feeDisplay, setFeeDisplay] = useState<FeeDisplay>(FeeDisplay.TX_FEE); const [feeDisplay, setFeeDisplay] = useState<FeeDisplay>(FeeDisplay.TX_FEE);
const feeDisplayToggler = () => { const feeDisplayToggler = () => {
if (feeDisplay === FeeDisplay.TX_FEE) { setFeeDisplay(feeDisplay + 1);
setFeeDisplay(FeeDisplay.GAS_PRICE); if (feeDisplay === FeeDisplay.GAS_PRICE) {
} else { setFeeDisplay(0);
setFeeDisplay(FeeDisplay.TX_FEE);
} }
}; };

View File

@ -7,6 +7,18 @@ import AggregatorV3Interface from "@chainlink/contracts/abi/v0.8/AggregatorV3Int
export const useETHUSDOracle = ( export const useETHUSDOracle = (
provider: JsonRpcProvider | undefined, provider: JsonRpcProvider | undefined,
blockTag: BlockTag | undefined blockTag: BlockTag | undefined
) => {
const priceMap = useMultipleETHUSDOracle(provider, [blockTag]);
if (blockTag === undefined) {
return undefined;
}
return priceMap[blockTag];
};
export const useMultipleETHUSDOracle = (
provider: JsonRpcProvider | undefined,
blockTags: (BlockTag | undefined)[]
) => { ) => {
const ethFeed = useMemo(() => { const ethFeed = useMemo(() => {
if (!provider || provider.network.chainId !== 1) { if (!provider || provider.network.chainId !== 1) {
@ -21,22 +33,45 @@ export const useETHUSDOracle = (
} }
}, [provider]); }, [provider]);
const [latestPriceData, setLatestPriceData] = useState<BigNumber>(); const [latestPriceData, setLatestPriceData] = useState<
Record<BlockTag, BigNumber>
>({});
useEffect(() => { useEffect(() => {
if (!ethFeed || !blockTag) { if (!ethFeed) {
return; return;
} }
const priceReaders: Promise<BigNumber | undefined>[] = [];
for (const blockTag of blockTags) {
priceReaders.push(
(async () => {
try {
const priceData = await ethFeed.latestRoundData({ blockTag });
return BigNumber.from(priceData.answer);
} catch (err) {
console.error(err);
return undefined;
}
})()
);
}
const readData = async () => { const readData = async () => {
try { const results = await Promise.all(priceReaders);
const priceData = await ethFeed.latestRoundData({ blockTag }); const priceMap: Record<BlockTag, BigNumber> = {};
setLatestPriceData(BigNumber.from(priceData.answer)); for (let i = 0; i < blockTags.length; i++) {
} catch (err) { const blockTag = blockTags[i];
console.error(err); const result = results[i];
if (blockTag === undefined || result === undefined) {
continue;
}
priceMap[blockTag] = result;
} }
setLatestPriceData(priceMap);
}; };
readData(); readData();
}, [ethFeed, blockTag]); }, [ethFeed, blockTags]);
return latestPriceData; return latestPriceData;
}; };