From 9f5f223b0f5f6eccea61025a943358abca7c3685 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 02:08:58 -0300 Subject: [PATCH 1/9] Convert useETHUSDOracle to SWR --- src/usePriceOracle.ts | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts index f39230f..2780e5a 100644 --- a/src/usePriceOracle.ts +++ b/src/usePriceOracle.ts @@ -70,17 +70,36 @@ export const useTokenUSDOracle = ( return data ?? [undefined, undefined]; }; +const ethUSDFetcherKey = (blockTag: BlockTag | undefined) => { + if (blockTag === undefined) { + return null; + } + return ["ethusd", blockTag]; +}; + +const ethUSDFetcher = + ( + provider: JsonRpcProvider | undefined + ): Fetcher => + async (_, blockTag) => { + if (provider?.network.chainId !== 1) { + return undefined; + } + const c = new Contract("eth-usd.data.eth", AggregatorV3Interface, provider); + const priceData = await c.latestRoundData({ blockTag }); + return BigNumber.from(priceData.answer); + }; + export const useETHUSDOracle = ( provider: JsonRpcProvider | undefined, blockTag: BlockTag | undefined -) => { - const blockTags = useMemo(() => [blockTag], [blockTag]); - const priceMap = useMultipleETHUSDOracle(provider, blockTags); - - if (blockTag === undefined) { +): BigNumber | undefined => { + const fetcher = ethUSDFetcher(provider); + const { data, error } = useSWRImmutable(ethUSDFetcherKey(blockTag), fetcher); + if (error) { return undefined; } - return priceMap[blockTag]; + return data; }; export const useMultipleETHUSDOracle = ( From 57334f1f8fc7ccd92032fc8e49e22d0634a0a197 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 04:50:49 -0300 Subject: [PATCH 2/9] Get rid of price maps --- src/Block.tsx | 24 +-------- src/BlockReward.tsx | 47 ++++++++++++++++ src/BlockTransactions.tsx | 1 - src/address/AddressBalance.tsx | 32 +++++++++++ src/address/AddressTransactionResults.tsx | 29 +--------- src/block/BlockTransactionResults.tsx | 17 +----- src/components/ETH2USDValue.tsx | 33 ------------ src/components/FiatValue.tsx | 25 +++++++++ src/components/TransactionDetailsValue.tsx | 41 ++++++++++++++ src/search/TransactionItem.tsx | 18 ++----- src/search/TransactionItemFiatFee.tsx | 25 +++++++++ src/transaction/Details.tsx | 26 +++------ src/transaction/TransactionFee.tsx | 36 +++++++++++++ src/usePriceOracle.ts | 63 ---------------------- 14 files changed, 223 insertions(+), 194 deletions(-) create mode 100644 src/BlockReward.tsx create mode 100644 src/address/AddressBalance.tsx delete mode 100644 src/components/ETH2USDValue.tsx create mode 100644 src/components/FiatValue.tsx create mode 100644 src/components/TransactionDetailsValue.tsx create mode 100644 src/search/TransactionItemFiatFee.tsx create mode 100644 src/transaction/TransactionFee.tsx diff --git a/src/Block.tsx b/src/Block.tsx index e8a6c92..9a7e4e7 100644 --- a/src/Block.tsx +++ b/src/Block.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useMemo, useContext } from "react"; import { useParams, NavLink } from "react-router-dom"; -import { BigNumber } from "@ethersproject/bignumber"; import { commify } from "@ethersproject/units"; import { toUtf8String } from "@ethersproject/strings"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -12,13 +11,13 @@ import ContentFrame from "./ContentFrame"; import BlockNotFound from "./components/BlockNotFound"; import InfoRow from "./components/InfoRow"; import Timestamp from "./components/Timestamp"; +import BlockReward from "./BlockReward"; import GasValue from "./components/GasValue"; import PercentageBar from "./components/PercentageBar"; import BlockLink from "./components/BlockLink"; import DecoratedAddressLink from "./components/DecoratedAddressLink"; import TransactionValue from "./components/TransactionValue"; import FormattedBalance from "./components/FormattedBalance"; -import ETH2USDValue from "./components/ETH2USDValue"; import USDValue from "./components/USDValue"; import HexValue from "./components/HexValue"; import { RuntimeContext } from "./useRuntime"; @@ -55,7 +54,6 @@ const Block: React.FC = () => { }, [block]); const burntFees = block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed); - const netFeeReward = block?.feeReward ?? BigNumber.from(0); const gasUsedPerc = block && block.gasUsed.mul(10000).div(block.gasLimit).toNumber() / 100; @@ -100,25 +98,7 @@ const Block: React.FC = () => { - - {!netFeeReward.isZero() && ( - <> - {" "} - ( +{" "} - ) - - )} - {blockETHUSDPrice && ( - <> - {" "} - - - - - )} + diff --git a/src/BlockReward.tsx b/src/BlockReward.tsx new file mode 100644 index 0000000..2d8c64a --- /dev/null +++ b/src/BlockReward.tsx @@ -0,0 +1,47 @@ +import React, { useContext } from "react"; +import { BigNumber } from "@ethersproject/bignumber"; +import TransactionValue from "./components/TransactionValue"; +import FiatValue from "./components/FiatValue"; +import { RuntimeContext } from "./useRuntime"; +import { ExtendedBlock } from "./useErigonHooks"; +import { useETHUSDOracle } from "./usePriceOracle"; + +type BlockRewardProps = { + block: ExtendedBlock; +}; + +const BlockReward: React.FC = ({ block }) => { + const { provider } = useContext(RuntimeContext); + const eth2USDValue = useETHUSDOracle(provider, block.number); + + const netFeeReward = block?.feeReward ?? BigNumber.from(0); + const value = eth2USDValue + ? block.blockReward + .add(netFeeReward) + .mul(eth2USDValue) + .div(10 ** 8) + : undefined; + + return ( + <> + + {!netFeeReward.isZero() && ( + <> + {" "} + ( +{" "} + ) + + )} + {value && ( + <> + {" "} + + + + + )} + + ); +}; + +export default BlockReward; diff --git a/src/BlockTransactions.tsx b/src/BlockTransactions.tsx index b1fe5af..5396593 100644 --- a/src/BlockTransactions.tsx +++ b/src/BlockTransactions.tsx @@ -40,7 +40,6 @@ const BlockTransactions: React.FC = () => { = ({ balance }) => { + const { provider } = useContext(RuntimeContext); + const eth2USDValue = useETHUSDOracle(provider, "latest"); + const fiatValue = + !balance.isZero() && eth2USDValue !== undefined + ? balance.mul(eth2USDValue).div(10 ** 8) + : undefined; + + return ( + <> + + {fiatValue && ( + + + + )} + + ); +}; + +export default AddressBalance; diff --git a/src/address/AddressTransactionResults.tsx b/src/address/AddressTransactionResults.tsx index a6e1fc3..8e9ff39 100644 --- a/src/address/AddressTransactionResults.tsx +++ b/src/address/AddressTransactionResults.tsx @@ -1,9 +1,7 @@ import React, { useContext, useEffect, useMemo, useState } from "react"; -import { BlockTag } from "@ethersproject/providers"; import ContentFrame from "../ContentFrame"; import InfoRow from "../components/InfoRow"; -import TransactionValue from "../components/TransactionValue"; -import ETH2USDValue from "../components/ETH2USDValue"; +import AddressBalance from "./AddressBalance"; import TransactionAddress from "../components/TransactionAddress"; import Copy from "../components/Copy"; import TransactionLink from "../components/TransactionLink"; @@ -14,7 +12,6 @@ import TransactionItem from "../search/TransactionItem"; import UndefinedPageControl from "../search/UndefinedPageControl"; import { useFeeToggler } from "../search/useFeeToggler"; import { SelectionContext, useSelection } from "../useSelection"; -import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { RuntimeContext } from "../useRuntime"; import { useParams, useSearchParams } from "react-router-dom"; import { ChecksummedAddress, ProcessedTransaction } from "../types"; @@ -98,19 +95,6 @@ const AddressTransactionResults: React.FC = ({ const page = useMemo(() => controller?.getPage(), [controller]); - // Extract block number from all txs on current page - // TODO: dedup blockTags - const blockTags: BlockTag[] = useMemo(() => { - if (!page) { - return ["latest"]; - } - - const blockTags: BlockTag[] = page.map((t) => t.blockNumber); - blockTags.push("latest"); - return blockTags; - }, [page]); - const priceMap = useMultipleETHUSDOracle(provider, blockTags); - const balance = useAddressBalance(provider, address); const creator = useContractCreator(provider, address); @@ -121,15 +105,7 @@ const AddressTransactionResults: React.FC = ({ {balance && (
- - {!balance.isZero() && priceMap["latest"] !== undefined && ( - - - - )} +
)} @@ -163,7 +139,6 @@ const AddressTransactionResults: React.FC = ({ tx={tx} selectedAddress={address} feeDisplay={feeDisplay} - priceMap={priceMap} /> ))} diff --git a/src/block/BlockTransactionResults.tsx b/src/block/BlockTransactionResults.tsx index 5c1824f..14821ea 100644 --- a/src/block/BlockTransactionResults.tsx +++ b/src/block/BlockTransactionResults.tsx @@ -1,35 +1,27 @@ -import React, { useContext, useMemo } from "react"; -import { BlockTag } from "@ethersproject/abstract-provider"; +import React from "react"; import ContentFrame from "../ContentFrame"; import PageControl from "../search/PageControl"; import ResultHeader from "../search/ResultHeader"; import PendingResults from "../search/PendingResults"; import TransactionItem from "../search/TransactionItem"; import { useFeeToggler } from "../search/useFeeToggler"; -import { RuntimeContext } from "../useRuntime"; import { SelectionContext, useSelection } from "../useSelection"; import { ProcessedTransaction } from "../types"; import { PAGE_SIZE } from "../params"; -import { useMultipleETHUSDOracle } from "../usePriceOracle"; type BlockTransactionResultsProps = { - blockTag: BlockTag; page?: ProcessedTransaction[]; total: number; pageNumber: number; }; const BlockTransactionResults: React.FC = ({ - blockTag, page, total, pageNumber, }) => { - const { provider } = useContext(RuntimeContext); const selectionCtx = useSelection(); const [feeDisplay, feeDisplayToggler] = useFeeToggler(); - const blockTags = useMemo(() => [blockTag], [blockTag]); - const priceMap = useMultipleETHUSDOracle(provider, blockTags); return ( @@ -54,12 +46,7 @@ const BlockTransactionResults: React.FC = ({ {page ? ( {page.map((tx) => ( - + ))}
diff --git a/src/components/ETH2USDValue.tsx b/src/components/ETH2USDValue.tsx deleted file mode 100644 index 53ece53..0000000 --- a/src/components/ETH2USDValue.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import { BigNumber, FixedNumber } from "@ethersproject/bignumber"; -import { commify } from "@ethersproject/units"; - -type ETH2USDValueProps = { - ethAmount: BigNumber; - eth2USDValue: BigNumber; -}; - -/** - * Basic display of ETH -> USD values WITHOUT box decoration, only - * text formatting. - * - * USD amounts are displayed commified with 2 decimals places and $ prefix, - * i.e., "$1,000.00". - */ -const ETH2USDValue: React.FC = ({ - ethAmount, - eth2USDValue, -}) => { - const value = ethAmount.mul(eth2USDValue).div(10 ** 8); - - return ( - - $ - - {commify(FixedNumber.fromValue(value, 18).round(2).toString())} - - - ); -}; - -export default React.memo(ETH2USDValue); diff --git a/src/components/FiatValue.tsx b/src/components/FiatValue.tsx new file mode 100644 index 0000000..ae950c9 --- /dev/null +++ b/src/components/FiatValue.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { BigNumber, FixedNumber } from "@ethersproject/bignumber"; +import { commify } from "@ethersproject/units"; + +type FiatValueProps = { + value: BigNumber; +}; + +/** + * Basic display of ETH -> USD values WITHOUT box decoration, only + * text formatting. + * + * USD amounts are displayed commified with 2 decimals places and $ prefix, + * i.e., "$1,000.00". + */ +const FiatValue: React.FC = ({ value }) => ( + + $ + + {commify(FixedNumber.fromValue(value, 18).round(2).toString())} + + +); + +export default FiatValue; diff --git a/src/components/TransactionDetailsValue.tsx b/src/components/TransactionDetailsValue.tsx new file mode 100644 index 0000000..4d09e25 --- /dev/null +++ b/src/components/TransactionDetailsValue.tsx @@ -0,0 +1,41 @@ +import React, { useContext } from "react"; +import { BlockTag } from "@ethersproject/providers"; +import { BigNumber } from "@ethersproject/bignumber"; +import FormattedBalance from "./FormattedBalance"; +import { RuntimeContext } from "../useRuntime"; +import { useChainInfo } from "../useChainInfo"; +import { useETHUSDOracle } from "../usePriceOracle"; +import FiatValue from "./FiatValue"; + +type TransactionDetailsValueProps = { + blockTag: BlockTag | undefined; + value: BigNumber; +}; + +const TransactionDetailsValue: React.FC = ({ + blockTag, + value, +}) => { + const { provider } = useContext(RuntimeContext); + const blockETHUSDPrice = useETHUSDOracle(provider, blockTag); + const { + nativeCurrency: { symbol }, + } = useChainInfo(); + const fiatValue = + !value.isZero() && blockETHUSDPrice !== undefined + ? value.mul(blockETHUSDPrice).div(10 ** 8) + : undefined; + + return ( + <> + {symbol}{" "} + {fiatValue && ( + + + + )} + + ); +}; + +export default TransactionDetailsValue; diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index 3fee32c..94e4820 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -1,6 +1,4 @@ import React, { useContext } from "react"; -import { BlockTag } from "@ethersproject/abstract-provider"; -import { BigNumber } from "@ethersproject/bignumber"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle"; import MethodName from "../components/MethodName"; @@ -14,25 +12,23 @@ import TransactionDirection, { Flags, } from "../components/TransactionDirection"; import TransactionValue from "../components/TransactionValue"; +import TransactionItemFiatFee from "./TransactionItemFiatFee"; import { ProcessedTransaction } from "../types"; import { FeeDisplay } from "./useFeeToggler"; import { RuntimeContext } from "../useRuntime"; import { useHasCode } from "../useErigonHooks"; import { formatValue } from "../components/formatter"; -import ETH2USDValue from "../components/ETH2USDValue"; type TransactionItemProps = { tx: ProcessedTransaction; selectedAddress?: string; feeDisplay: FeeDisplay; - priceMap: Record; }; const TransactionItem: React.FC = ({ tx, selectedAddress, feeDisplay, - priceMap, }) => { const { provider } = useContext(RuntimeContext); const toHasCode = useHasCode( @@ -130,15 +126,9 @@ const TransactionItem: React.FC = ({ {feeDisplay === FeeDisplay.TX_FEE && formatValue(tx.fee, 18)} - {feeDisplay === FeeDisplay.TX_FEE_USD && - (priceMap[tx.blockNumber] ? ( - - ) : ( - "N/A" - ))} + {feeDisplay === FeeDisplay.TX_FEE_USD && ( + + )} {feeDisplay === FeeDisplay.GAS_PRICE && formatValue(tx.gasPrice, 9)}
diff --git a/src/search/TransactionItemFiatFee.tsx b/src/search/TransactionItemFiatFee.tsx new file mode 100644 index 0000000..813bd22 --- /dev/null +++ b/src/search/TransactionItemFiatFee.tsx @@ -0,0 +1,25 @@ +import React, { useContext } from "react"; +import { BlockTag } from "@ethersproject/providers"; +import { BigNumber } from "@ethersproject/bignumber"; +import FiatValue from "../components/FiatValue"; +import { RuntimeContext } from "../useRuntime"; +import { useETHUSDOracle } from "../usePriceOracle"; + +type TransactionItemFiatFeeProps = { + blockTag: BlockTag; + fee: BigNumber; +}; + +const TransactionItemFiatFee: React.FC = ({ + blockTag, + fee, +}) => { + const { provider } = useContext(RuntimeContext); + const eth2USDValue = useETHUSDOracle(provider, blockTag); + const fiatValue = + eth2USDValue !== undefined ? fee.mul(eth2USDValue).div(10 ** 8) : undefined; + + return fiatValue ? : <>N/A; +}; + +export default TransactionItemFiatFee; diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index a34a212..5b6e972 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -17,12 +17,13 @@ import NavNonce from "./NavNonce"; import Timestamp from "../components/Timestamp"; import InternalTransactionOperation from "../components/InternalTransactionOperation"; import MethodName from "../components/MethodName"; +import TransactionDetailsValue from "../components/TransactionDetailsValue"; import TransactionType from "../components/TransactionType"; +import TransactionFee from "./TransactionFee"; import RewardSplit from "./RewardSplit"; import GasValue from "../components/GasValue"; import USDValue from "../components/USDValue"; import FormattedBalance from "../components/FormattedBalance"; -import ETH2USDValue from "../components/ETH2USDValue"; import TokenTransferItem from "../TokenTransferItem"; import { TransactionData } from "../types"; import PercentageBar from "../components/PercentageBar"; @@ -308,15 +309,10 @@ const Details: React.FC = ({ txData }) => { )} - {symbol}{" "} - {!txData.value.isZero() && blockETHUSDPrice && ( - - - - )} + = ({ txData }) => {
- {symbol}{" "} - {blockETHUSDPrice && ( - - - - )} +
{hasEIP1559 && }
diff --git a/src/transaction/TransactionFee.tsx b/src/transaction/TransactionFee.tsx new file mode 100644 index 0000000..7324b46 --- /dev/null +++ b/src/transaction/TransactionFee.tsx @@ -0,0 +1,36 @@ +import React, { useContext } from "react"; +import FormattedBalance from "../components/FormattedBalance"; +import FiatValue from "../components/FiatValue"; +import { RuntimeContext } from "../useRuntime"; +import { useETHUSDOracle } from "../usePriceOracle"; +import { useChainInfo } from "../useChainInfo"; +import { ConfirmedTransactionData } from "../types"; + +type TransactionFeeProps = { + confirmedData: ConfirmedTransactionData; +}; + +const TransactionFee: React.FC = ({ confirmedData }) => { + const { provider } = useContext(RuntimeContext); + const blockETHUSDPrice = useETHUSDOracle(provider, confirmedData.blockNumber); + const { + nativeCurrency: { symbol }, + } = useChainInfo(); + const fiatValue = + blockETHUSDPrice !== undefined + ? confirmedData.fee.mul(blockETHUSDPrice).div(10 ** 8) + : undefined; + + return ( + <> + {symbol}{" "} + {fiatValue && ( + + + + )} + + ); +}; + +export default TransactionFee; diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts index 2780e5a..783d76e 100644 --- a/src/usePriceOracle.ts +++ b/src/usePriceOracle.ts @@ -1,4 +1,3 @@ -import { useEffect, useMemo, useState } from "react"; import { JsonRpcProvider, BlockTag } from "@ethersproject/providers"; import { Contract } from "@ethersproject/contracts"; import { BigNumber } from "@ethersproject/bignumber"; @@ -101,65 +100,3 @@ export const useETHUSDOracle = ( } return data; }; - -export const useMultipleETHUSDOracle = ( - provider: JsonRpcProvider | undefined, - blockTags: (BlockTag | undefined)[] -) => { - const ethFeed = useMemo(() => { - // TODO: it currently is hardcoded to support only mainnet - if (!provider || provider.network.chainId !== 1) { - return undefined; - } - - try { - return new Contract("eth-usd.data.eth", AggregatorV3Interface, provider); - } catch (err) { - console.error(err); - return undefined; - } - }, [provider]); - - const [latestPriceData, setLatestPriceData] = useState< - Record - >({}); - useEffect(() => { - if (!ethFeed) { - return; - } - - const priceReaders: Promise[] = []; - for (const blockTag of blockTags) { - priceReaders.push( - (async () => { - try { - const priceData = await ethFeed.latestRoundData({ blockTag }); - return BigNumber.from(priceData.answer); - } catch (err) { - // Silently ignore on purpose; it means the network or block number does - // not contain the chainlink feed contract - return undefined; - } - })() - ); - } - const readData = async () => { - const results = await Promise.all(priceReaders); - const priceMap: Record = {}; - for (let i = 0; i < blockTags.length; i++) { - const blockTag = blockTags[i]; - const result = results[i]; - if (blockTag === undefined || result === undefined) { - continue; - } - - priceMap[blockTag] = result; - } - - setLatestPriceData(priceMap); - }; - readData(); - }, [ethFeed, blockTags]); - - return latestPriceData; -}; From 6c7fbd8f353f11ea9cde124e49f4854422c68da5 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 08:04:05 -0300 Subject: [PATCH 3/9] Take advantage of SWR to not do prop drilling --- src/components/InternalTransactionOperation.tsx | 11 ++--------- src/components/InternalTransfer.tsx | 13 +++++++------ src/transaction/Details.tsx | 1 - 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/components/InternalTransactionOperation.tsx b/src/components/InternalTransactionOperation.tsx index 3037dd2..d102be9 100644 --- a/src/components/InternalTransactionOperation.tsx +++ b/src/components/InternalTransactionOperation.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { BigNumber } from "@ethersproject/bignumber"; import InternalTransfer from "./InternalTransfer"; import InternalSelfDestruct from "./InternalSelfDestruct"; import InternalCreate from "./InternalCreate"; @@ -8,20 +7,14 @@ import { TransactionData, InternalOperation, OperationType } from "../types"; type InternalTransactionOperationProps = { txData: TransactionData; internalOp: InternalOperation; - // TODO: migrate all this logic to SWR - ethUSDPrice: BigNumber | undefined; }; const InternalTransactionOperation: React.FC< InternalTransactionOperationProps -> = ({ txData, internalOp, ethUSDPrice }) => ( +> = ({ txData, internalOp }) => ( <> {internalOp.type === OperationType.TRANSFER && ( - + )} {internalOp.type === OperationType.SELF_DESTRUCT && ( diff --git a/src/components/InternalTransfer.tsx b/src/components/InternalTransfer.tsx index 173c74e..0faeb77 100644 --- a/src/components/InternalTransfer.tsx +++ b/src/components/InternalTransfer.tsx @@ -1,5 +1,4 @@ import React, { useContext } from "react"; -import { BigNumber } from "@ethersproject/bignumber"; import { formatEther } from "@ethersproject/units"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight"; @@ -11,19 +10,17 @@ import USDAmount from "./USDAmount"; import { RuntimeContext } from "../useRuntime"; import { useHasCode } from "../useErigonHooks"; import { useChainInfo } from "../useChainInfo"; +import { useETHUSDOracle } from "../usePriceOracle"; import { TransactionData, InternalOperation } from "../types"; type InternalTransferProps = { txData: TransactionData; internalOp: InternalOperation; - // TODO: migrate all this logic to SWR - ethUSDPrice: BigNumber | undefined; }; const InternalTransfer: React.FC = ({ txData, internalOp, - ethUSDPrice, }) => { const { nativeCurrency: { symbol, decimals }, @@ -36,6 +33,10 @@ const InternalTransfer: React.FC = ({ internalOp.to === txData.confirmedData.miner; const { provider } = useContext(RuntimeContext); + const blockETHUSDPrice = useETHUSDOracle( + provider, + txData.confirmedData?.blockNumber + ); const fromHasCode = useHasCode( provider, internalOp.from, @@ -99,12 +100,12 @@ const InternalTransfer: React.FC = ({ {formatEther(internalOp.value)} {symbol} - {ethUSDPrice && ( + {blockETHUSDPrice && ( diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 5b6e972..5db70f2 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -286,7 +286,6 @@ const Details: React.FC = ({ txData }) => { key={i} txData={txData} internalOp={op} - ethUSDPrice={blockETHUSDPrice} /> ))}
From 4fcf886808a53aa48b8f1d9c45c47259cd56dcf4 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 08:10:32 -0300 Subject: [PATCH 4/9] Add TODO --- src/useLatestBlock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/useLatestBlock.ts b/src/useLatestBlock.ts index a38a4ef..dc77e69 100644 --- a/src/useLatestBlock.ts +++ b/src/useLatestBlock.ts @@ -10,6 +10,7 @@ export const useLatestBlock = (provider?: JsonRpcProvider) => { return; } + // TODO: remove duplicated code const readLatestBlock = async () => { const blockNum = await provider.getBlockNumber(); const _raw = await provider.send("erigon_getHeaderByNumber", [blockNum]); From d0f68000be0f3c13c37575bc45e83d3d407f5f94 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 16:05:37 -0300 Subject: [PATCH 5/9] Fetch chainInfo using SWR --- src/useChainInfo.ts | 52 ++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/useChainInfo.ts b/src/useChainInfo.ts index dec3798..90b6482 100644 --- a/src/useChainInfo.ts +++ b/src/useChainInfo.ts @@ -1,4 +1,5 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext } from "react"; +import useSWRImmutable from "swr/immutable"; import { chainInfoURL } from "./url"; import { OtterscanRuntime } from "./useRuntime"; @@ -24,40 +25,33 @@ export const defaultChainInfo: ChainInfo = { export const ChainInfoContext = createContext(undefined); +const chainInfoFetcher = async (assetsURLPrefix: string, chainId: number) => { + const url = chainInfoURL(assetsURLPrefix, chainId); + const res = await fetch(url); + if (!res.ok) { + return defaultChainInfo; + } + + const info: ChainInfo = await res.json(); + return info; +}; + export const useChainInfoFromMetadataFile = ( runtime: OtterscanRuntime | undefined ): ChainInfo | undefined => { const assetsURLPrefix = runtime?.config?.assetsURLPrefix; const chainId = runtime?.provider?.network.chainId; - const [chainInfo, setChainInfo] = useState(undefined); - - useEffect(() => { - if (assetsURLPrefix === undefined || chainId === undefined) { - setChainInfo(undefined); - return; - } - - const readChainInfo = async () => { - try { - const res = await fetch(chainInfoURL(assetsURLPrefix, chainId)); - if (!res.ok) { - setChainInfo(defaultChainInfo); - return; - } - - const info: ChainInfo = await res.json(); - setChainInfo(info); - } catch (err) { - // ignore - setChainInfo(defaultChainInfo); - return; - } - }; - readChainInfo(); - }, [assetsURLPrefix, chainId]); - - return chainInfo; + const { data, error } = useSWRImmutable( + assetsURLPrefix !== undefined && chainId !== undefined + ? [assetsURLPrefix, chainId] + : null, + chainInfoFetcher + ); + if (error) { + return defaultChainInfo; + } + return data; }; export const useChainInfo = (): ChainInfo => { From d13671b75dac85b1f5cd6ed8841525099c9c03d6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 16:26:24 -0300 Subject: [PATCH 6/9] Improve docs --- src/useLatestBlock.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/useLatestBlock.ts b/src/useLatestBlock.ts index dc77e69..ba5e21a 100644 --- a/src/useLatestBlock.ts +++ b/src/useLatestBlock.ts @@ -36,6 +36,11 @@ export const useLatestBlock = (provider?: JsonRpcProvider) => { return latestBlock; }; +/** + * Returns the latest block number AND hook an internal listener + * that'll update and trigger a component render as a side effect + * every time it is notified of a new block by the web3 provider. + */ export const useLatestBlockNumber = (provider?: JsonRpcProvider) => { const [latestBlock, setLatestBlock] = useState(); @@ -44,12 +49,15 @@ export const useLatestBlockNumber = (provider?: JsonRpcProvider) => { return; } + // Immediately read and set the latest block number const readLatestBlock = async () => { const blockNum = await provider.getBlockNumber(); setLatestBlock(blockNum); }; readLatestBlock(); + // Hook a listener that'll update the latest block number + // every time it is notified of a new block const listener = async (blockNumber: number) => { setLatestBlock(blockNumber); }; From 746a908e7a5d1694319931a73157315d10f99bf0 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 16:42:31 -0300 Subject: [PATCH 7/9] Improve docs; remove code duplication --- src/Home.tsx | 4 ++-- src/PriceBox.tsx | 4 ++-- src/special/london/London.tsx | 4 ++-- src/useLatestBlock.ts | 33 ++++++++++++++++++++------------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Home.tsx b/src/Home.tsx index 0509330..474c3d4 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -7,7 +7,7 @@ import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode"; import Logo from "./Logo"; import Timestamp from "./components/Timestamp"; import { RuntimeContext } from "./useRuntime"; -import { useLatestBlock } from "./useLatestBlock"; +import { useLatestBlockHeader } from "./useLatestBlock"; import { blockURL } from "./url"; import { useGenericSearch } from "./search/search"; @@ -17,7 +17,7 @@ const Home: React.FC = () => { const { provider } = useContext(RuntimeContext); const [searchRef, handleChange, handleSubmit] = useGenericSearch(); - const latestBlock = useLatestBlock(provider); + const latestBlock = useLatestBlockHeader(provider); const [isScanning, setScanning] = useState(false); document.title = "Home | Otterscan"; diff --git a/src/PriceBox.tsx b/src/PriceBox.tsx index 1e2586a..60b0903 100644 --- a/src/PriceBox.tsx +++ b/src/PriceBox.tsx @@ -6,7 +6,7 @@ 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 { useLatestBlock } from "./useLatestBlock"; +import { useLatestBlockHeader } from "./useLatestBlock"; import { useChainInfo } from "./useChainInfo"; const ETH_FEED_DECIMALS = 8; @@ -17,7 +17,7 @@ const PriceBox: React.FC = () => { const { nativeCurrency: { symbol }, } = useChainInfo(); - const latestBlock = useLatestBlock(provider); + const latestBlock = useLatestBlockHeader(provider); const maybeOutdated: boolean = latestBlock !== undefined && diff --git a/src/special/london/London.tsx b/src/special/london/London.tsx index 60e22f2..369d215 100644 --- a/src/special/london/London.tsx +++ b/src/special/london/London.tsx @@ -1,5 +1,5 @@ import React, { useContext } from "react"; -import { useLatestBlock } from "../../useLatestBlock"; +import { useLatestBlockHeader } from "../../useLatestBlock"; import { RuntimeContext } from "../../useRuntime"; import Countdown from "./Countdown"; import Blocks from "./Blocks"; @@ -7,7 +7,7 @@ import { londonBlockNumber } from "./params"; const London: React.FC = () => { const { provider } = useContext(RuntimeContext); - const block = useLatestBlock(provider); + const block = useLatestBlockHeader(provider); if (!provider || !block) { return
; } diff --git a/src/useLatestBlock.ts b/src/useLatestBlock.ts index ba5e21a..ef26cde 100644 --- a/src/useLatestBlock.ts +++ b/src/useLatestBlock.ts @@ -2,7 +2,12 @@ import { useState, useEffect } from "react"; import { Block } from "@ethersproject/abstract-provider"; import { JsonRpcProvider } from "@ethersproject/providers"; -export const useLatestBlock = (provider?: JsonRpcProvider) => { +/** + * Returns the latest block header AND hook an internal listener + * that'll update and trigger a component render as a side effect + * every time it is notified of a new block by the web3 provider. + */ +export const useLatestBlockHeader = (provider?: JsonRpcProvider) => { const [latestBlock, setLatestBlock] = useState(); useEffect(() => { @@ -10,16 +15,7 @@ export const useLatestBlock = (provider?: JsonRpcProvider) => { return; } - // TODO: remove duplicated code - const readLatestBlock = async () => { - const blockNum = await provider.getBlockNumber(); - const _raw = await provider.send("erigon_getHeaderByNumber", [blockNum]); - const _block = provider.formatter.block(_raw); - setLatestBlock(_block); - }; - readLatestBlock(); - - const listener = async (blockNumber: number) => { + const getAndSetBlockHeader = async (blockNumber: number) => { const _raw = await provider.send("erigon_getHeaderByNumber", [ blockNumber, ]); @@ -27,9 +23,18 @@ export const useLatestBlock = (provider?: JsonRpcProvider) => { setLatestBlock(_block); }; - provider.on("block", listener); + // Immediately read and set the latest block header + const readLatestBlock = async () => { + const blockNum = await provider.getBlockNumber(); + await getAndSetBlockHeader(blockNum); + }; + readLatestBlock(); + + // Hook a listener that'll update the latest block header + // every time it is notified of a new block + provider.on("block", getAndSetBlockHeader); return () => { - provider.removeListener("block", listener); + provider.removeListener("block", getAndSetBlockHeader); }; }, [provider]); @@ -40,6 +45,8 @@ export const useLatestBlock = (provider?: JsonRpcProvider) => { * Returns the latest block number AND hook an internal listener * that'll update and trigger a component render as a side effect * every time it is notified of a new block by the web3 provider. + * + * This hook is cheaper than useLatestBlockHeader. */ export const useLatestBlockNumber = (provider?: JsonRpcProvider) => { const [latestBlock, setLatestBlock] = useState(); From 812539674e9d4bc0116092e365e97a0676ce96f2 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 17:56:10 -0300 Subject: [PATCH 8/9] Remove hacky prefetcher --- src/transaction/NavButton.tsx | 41 +++++++++++++++++++++++++++++------ src/transaction/NavNonce.tsx | 27 ++--------------------- src/useErigonHooks.ts | 21 +----------------- 3 files changed, 37 insertions(+), 52 deletions(-) diff --git a/src/transaction/NavButton.tsx b/src/transaction/NavButton.tsx index 3f7186b..9ea59bf 100644 --- a/src/transaction/NavButton.tsx +++ b/src/transaction/NavButton.tsx @@ -1,5 +1,7 @@ -import { PropsWithChildren } from "react"; +import React, { PropsWithChildren, useContext, useState } from "react"; import { NavLink } from "react-router-dom"; +import { RuntimeContext } from "../useRuntime"; +import { useTransactionBySenderAndNonce } from "../useErigonHooks"; import { ChecksummedAddress } from "../types"; import { addressByNonceURL } from "../url"; @@ -16,6 +18,8 @@ const NavButton: React.FC> = ({ disabled, children, }) => { + const [prefetch, setPrefetch] = useState(false); + if (disabled) { return ( @@ -25,13 +29,36 @@ const NavButton: React.FC> = ({ } return ( - - {children} - + <> + setPrefetch(true)} + > + {children} + + {prefetch && } + ); }; +type PrefetcherProps = { + checksummedAddress: ChecksummedAddress; + nonce: number; +}; + +const Prefetcher: React.FC = ({ + checksummedAddress, + nonce, +}) => { + const { provider } = useContext(RuntimeContext); + const _txHash = useTransactionBySenderAndNonce( + provider, + checksummedAddress, + nonce + ); + + return <>; +}; + export default NavButton; diff --git a/src/transaction/NavNonce.tsx b/src/transaction/NavNonce.tsx index 9efd47f..e5f55b9 100644 --- a/src/transaction/NavNonce.tsx +++ b/src/transaction/NavNonce.tsx @@ -1,15 +1,11 @@ -import React, { useContext, useEffect } from "react"; +import React, { useContext } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons/faChevronLeft"; import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight"; import NavButton from "./NavButton"; import { ChecksummedAddress } from "../types"; import { RuntimeContext } from "../useRuntime"; -import { - prefetchTransactionBySenderAndNonce, - useTransactionCount, -} from "../useErigonHooks"; -import { useSWRConfig } from "swr"; +import { useTransactionCount } from "../useErigonHooks"; type NavNonceProps = { sender: ChecksummedAddress; @@ -20,25 +16,6 @@ const NavNonce: React.FC = ({ sender, nonce }) => { const { provider } = useContext(RuntimeContext); const count = useTransactionCount(provider, sender); - // Prefetch - const swrConfig = useSWRConfig(); - useEffect(() => { - if (!provider || !sender || nonce === undefined || count === undefined) { - return; - } - - prefetchTransactionBySenderAndNonce(swrConfig, provider, sender, nonce - 1); - prefetchTransactionBySenderAndNonce(swrConfig, provider, sender, nonce + 1); - if (count > 0) { - prefetchTransactionBySenderAndNonce( - swrConfig, - provider, - sender, - count - 1 - ); - } - }, [swrConfig, provider, sender, nonce, count]); - return (
diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 6382877..f2b798c 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -10,7 +10,7 @@ import { Contract } from "@ethersproject/contracts"; import { defaultAbiCoder } from "@ethersproject/abi"; import { BigNumber } from "@ethersproject/bignumber"; import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes"; -import useSWR, { useSWRConfig } from "swr"; +import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; import { getInternalOperations } from "./nodeFunctions"; import { @@ -527,25 +527,6 @@ const getTransactionBySenderAndNonceFetcher = return result; }; -export const prefetchTransactionBySenderAndNonce = ( - { mutate }: ReturnType, - provider: JsonRpcProvider, - sender: ChecksummedAddress, - nonce: number -) => { - const key: TransactionBySenderAndNonceKey = { - network: provider.network.chainId, - sender, - nonce, - }; - mutate(key, (curr: any) => { - if (curr) { - return curr; - } - return getTransactionBySenderAndNonceFetcher(provider)(key); - }); -}; - export const useTransactionBySenderAndNonce = ( provider: JsonRpcProvider | undefined, sender: ChecksummedAddress | undefined, From bf2aa4e64450009bd8123f55d4f0f98de66062f7 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Mon, 22 Aug 2022 18:02:17 -0300 Subject: [PATCH 9/9] Encapsulate hook call --- src/Address.tsx | 5 +---- src/components/DecoratedAddressLink.tsx | 8 +------- src/sourcify/useSourcify.ts | 7 ++++--- src/transaction/Details.tsx | 8 +------- src/transaction/LogEntry.tsx | 8 +------- 5 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/Address.tsx b/src/Address.tsx index 583966f..07883a5 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -21,7 +21,6 @@ import SourcifyLogo from "./sourcify/SourcifyLogo"; import AddressTransactionResults from "./address/AddressTransactionResults"; import Contracts from "./address/Contracts"; import { RuntimeContext } from "./useRuntime"; -import { useAppConfigContext } from "./useAppConfig"; import { useAddressOrENS } from "./useResolvedAddresses"; import { useSourcifyMetadata } from "./sourcify/useSourcify"; import { ChecksummedAddress } from "./types"; @@ -66,11 +65,9 @@ const Address: React.FC = () => { }, [addressOrName, checksummedAddress, isENS]); const hasCode = useHasCode(provider, checksummedAddress, "latest"); - const { sourcifySource } = useAppConfigContext(); const addressMetadata = useSourcifyMetadata( hasCode ? checksummedAddress : undefined, - provider?.network.chainId, - sourcifySource + provider?.network.chainId ); const { network, faucets } = useChainInfo(); diff --git a/src/components/DecoratedAddressLink.tsx b/src/components/DecoratedAddressLink.tsx index 62ccd3a..4659e68 100644 --- a/src/components/DecoratedAddressLink.tsx +++ b/src/components/DecoratedAddressLink.tsx @@ -9,7 +9,6 @@ import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins"; import SourcifyLogo from "../sourcify/SourcifyLogo"; import PlainAddress from "./PlainAddress"; import { RuntimeContext } from "../useRuntime"; -import { useAppConfigContext } from "../useAppConfig"; import { useSourcifyMetadata } from "../sourcify/useSourcify"; import { useResolvedAddress } from "../useResolvedAddresses"; import { AddressContext, ChecksummedAddress, ZERO_ADDRESS } from "../types"; @@ -39,12 +38,7 @@ const DecoratedAddressLink: React.FC = ({ eoa, }) => { const { provider } = useContext(RuntimeContext); - const { sourcifySource } = useAppConfigContext(); - const metadata = useSourcifyMetadata( - address, - provider?.network.chainId, - sourcifySource - ); + const metadata = useSourcifyMetadata(address, provider?.network.chainId); const mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS; const burn = addressCtx === AddressContext.TO && address === ZERO_ADDRESS; diff --git a/src/sourcify/useSourcify.ts b/src/sourcify/useSourcify.ts index 0ae3958..1ef4150 100644 --- a/src/sourcify/useSourcify.ts +++ b/src/sourcify/useSourcify.ts @@ -4,6 +4,7 @@ import { ErrorDescription } from "@ethersproject/abi/lib/interface"; import useSWRImmutable from "swr/immutable"; import { ChecksummedAddress, TransactionData } from "../types"; import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "../url"; +import { useAppConfigContext } from "../useAppConfig"; export type UserMethod = { notice?: string | undefined; @@ -98,13 +99,13 @@ const sourcifyFetcher = async (url: string) => { export const useSourcifyMetadata = ( address: ChecksummedAddress | undefined, - chainId: number | undefined, - source: SourcifySource + chainId: number | undefined ): Metadata | null | undefined => { + const { sourcifySource } = useAppConfigContext(); const metadataURL = () => address === undefined || chainId === undefined ? null - : sourcifyMetadata(address, chainId, source); + : sourcifyMetadata(address, chainId, sourcifySource); const { data, error } = useSWRImmutable( metadataURL, sourcifyFetcher diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 5db70f2..62844ff 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -37,7 +37,6 @@ import { use4Bytes, useTransactionDescription, } from "../use4Bytes"; -import { useAppConfigContext } from "../useAppConfig"; import { useError, useSourcifyMetadata, @@ -82,12 +81,7 @@ const Details: React.FC = ({ txData }) => { return false; }, [txData, internalOps]); - const { sourcifySource } = useAppConfigContext(); - const metadata = useSourcifyMetadata( - txData?.to, - provider?.network.chainId, - sourcifySource - ); + const metadata = useSourcifyMetadata(txData?.to, provider?.network.chainId); const txDesc = useSourcifyTransactionDescription(metadata, txData); const userDoc = metadata?.output.userdoc; diff --git a/src/transaction/LogEntry.tsx b/src/transaction/LogEntry.tsx index d67d30e..9f8d691 100644 --- a/src/transaction/LogEntry.tsx +++ b/src/transaction/LogEntry.tsx @@ -9,7 +9,6 @@ import DecodedParamsTable from "./decoder/DecodedParamsTable"; import DecodedLogSignature from "./decoder/DecodedLogSignature"; import { useTopic0 } from "../useTopic0"; import { RuntimeContext } from "../useRuntime"; -import { useAppConfigContext } from "../useAppConfig"; import { useSourcifyMetadata } from "../sourcify/useSourcify"; type LogEntryProps = { @@ -18,12 +17,7 @@ type LogEntryProps = { const LogEntry: React.FC = ({ log }) => { const { provider } = useContext(RuntimeContext); - const { sourcifySource } = useAppConfigContext(); - const metadata = useSourcifyMetadata( - log.address, - provider?.network.chainId, - sourcifySource - ); + const metadata = useSourcifyMetadata(log.address, provider?.network.chainId); const logDesc = useMemo(() => { if (!metadata) {