From 90d25cdd8dcec32a5b3fc8f2b4b8ab9f2d0bcc86 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 06:33:42 -0300 Subject: [PATCH 01/11] Remove logic duplication --- src/PriceBox.tsx | 39 +++++--------------------------- src/usePriceOracle.ts | 52 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/PriceBox.tsx b/src/PriceBox.tsx index 60b0903..30ec31e 100644 --- a/src/PriceBox.tsx +++ b/src/PriceBox.tsx @@ -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(); - const [latestGasData, setLatestGasData] = useState(); - 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]; diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts index 783d76e..1956a52 100644 --- a/src/usePriceOracle.ts +++ b/src/usePriceOracle.ts @@ -79,20 +79,32 @@ const ethUSDFetcherKey = (blockTag: BlockTag | undefined) => { const ethUSDFetcher = ( provider: JsonRpcProvider | undefined - ): Fetcher => + ): 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); + 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 +112,39 @@ export const useETHUSDOracle = ( } return data; }; + +const fastGasFetcherKey = (blockTag: BlockTag | undefined) => { + if (blockTag === undefined) { + return null; + } + return ["gasgwei", blockTag]; +}; + +const fastGasFetcher = + ( + provider: JsonRpcProvider | undefined + ): Fetcher => + async (_, blockTag) => { + if (provider?.network.chainId !== 1) { + return undefined; + } + const c = new Contract( + "fast-gas-gwei.data.eth", + AggregatorV3Interface, + provider + ); + 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; +}; From c365bde78473c1447ab9fdeb7a2f61e00044ed2a Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 15:17:53 -0300 Subject: [PATCH 02/11] WIP: convert block/txs reading in SWR; simplify code --- src/nodeFunctions.ts | 23 ----------- src/useErigonHooks.ts | 96 ++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 60 deletions(-) delete mode 100644 src/nodeFunctions.ts diff --git a/src/nodeFunctions.ts b/src/nodeFunctions.ts deleted file mode 100644 index 5a6f140..0000000 --- a/src/nodeFunctions.ts +++ /dev/null @@ -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; -}; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index f2b798c..75e8a32 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Block, BlockWithTransactions, @@ -12,7 +12,6 @@ import { BigNumber } from "@ethersproject/bignumber"; import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes"; import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; -import { getInternalOperations } from "./nodeFunctions"; import { TokenMetas, TokenTransfer, @@ -169,24 +168,25 @@ export const useBlockTransactions = ( return [totalTxs, txs]; }; +const blockDataFetcher = async ( + provider: JsonRpcProvider, + blockNumberOrHash: string +) => { + return await readBlock(provider, blockNumberOrHash); +}; + export const useBlockData = ( provider: JsonRpcProvider | undefined, blockNumberOrHash: string ): ExtendedBlock | null | undefined => { - const [block, setBlock] = useState(); - useEffect(() => { - if (!provider) { - return undefined; - } - - const _readBlock = async () => { - const extBlock = await readBlock(provider, blockNumberOrHash); - setBlock(extBlock); - }; - _readBlock(); - }, [provider, blockNumberOrHash]); - - return block; + const { data, error } = useSWRImmutable( + provider !== undefined ? [provider, blockNumberOrHash] : null, + blockDataFetcher + ); + if (error) { + return undefined; + } + return data; }; export const useTxData = ( @@ -308,33 +308,55 @@ export const useTxData = ( return txData; }; +// TODO: convert caller to hooks and remove this function +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; +}; + export const useInternalOperations = ( provider: JsonRpcProvider | undefined, txData: TransactionData | undefined | null ): InternalOperation[] | undefined => { - const [intTransfers, setIntTransfers] = useState(); + const { data, error } = useSWRImmutable( + provider !== undefined && txData?.confirmedData + ? ["ots_getInternalOperations", txData.transactionHash] + : null, + providerFetcher(provider) + ); - useEffect(() => { - const traceTransfers = async () => { - if (!provider || !txData || !txData.confirmedData) { - return; - } + const _transfers = useMemo(() => { + if (provider === undefined || error || data === undefined) { + return undefined; + } - const _transfers = await getInternalOperations( - provider, - txData.transactionHash - ); - 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 _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 type TraceEntry = { From 21a549b3e1fe5cb776514a4c70a95f01a12bb29a Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 16:11:02 -0300 Subject: [PATCH 03/11] Push down flashbots checking logic --- src/components/TransactionDirection.tsx | 1 + src/search/TransactionItem.tsx | 7 +-- src/transaction/Details.tsx | 22 +++----- src/types.ts | 1 - src/useErigonHooks.ts | 68 +++++++++---------------- 5 files changed, 35 insertions(+), 64 deletions(-) diff --git a/src/components/TransactionDirection.tsx b/src/components/TransactionDirection.tsx index aca1f95..54b1685 100644 --- a/src/components/TransactionDirection.tsx +++ b/src/components/TransactionDirection.tsx @@ -11,6 +11,7 @@ export enum Direction { } export enum Flags { + // Means the transaction internal sends ETH to the miner, e.g. flashbots MINER, } diff --git a/src/search/TransactionItem.tsx b/src/search/TransactionItem.tsx index 94e4820..c0f6d39 100644 --- a/src/search/TransactionItem.tsx +++ b/src/search/TransactionItem.tsx @@ -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 = ({ 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 = ({ } } - const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction; + const flash = tx.gasPrice.isZero() && sendsToMiner; return (
= ({ diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 62844ff..6932f5c 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -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,7 @@ import { useTransactionDescription as useSourcifyTransactionDescription, } from "../sourcify/useSourcify"; import { RuntimeContext } from "../useRuntime"; -import { useInternalOperations, useTransactionError } from "../useErigonHooks"; +import { useSendsToMiner, useTransactionError } from "../useErigonHooks"; import { useChainInfo } from "../useChainInfo"; import { useETHUSDOracle } from "../usePriceOracle"; @@ -67,19 +67,11 @@ const Details: React.FC = ({ txData }) => { txData.value ); - const internalOps = useInternalOperations(provider, txData); - const sendsEthToMiner = useMemo(() => { - if (!txData || !internalOps) { - return false; - } - - for (const t of internalOps) { - if (t.to === txData.confirmedData?.miner) { - return true; - } - } - return false; - }, [txData, internalOps]); + const [sendsEthToMiner, internalOps] = useSendsToMiner( + provider, + txData.confirmedData ? txData.transactionHash : undefined, + txData.confirmedData?.miner + ); const metadata = useSourcifyMetadata(txData?.to, provider?.network.chainId); diff --git a/src/types.ts b/src/types.ts index adedc86..44a134b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,7 +18,6 @@ export type ProcessedTransaction = { from?: string; to: string | null; createdContractAddress?: string; - internalMinerInteraction?: boolean; value: BigNumber; fee: BigNumber; gasPrice: BigNumber; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 75e8a32..a3620b7 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -140,27 +140,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]); @@ -308,34 +287,13 @@ export const useTxData = ( return txData; }; -// TODO: convert caller to hooks and remove this function -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; -}; - export const useInternalOperations = ( provider: JsonRpcProvider | undefined, - txData: TransactionData | undefined | null + txHash: string | undefined ): InternalOperation[] | undefined => { const { data, error } = useSWRImmutable( - provider !== undefined && txData?.confirmedData - ? ["ots_getInternalOperations", txData.transactionHash] + provider !== undefined && txHash !== undefined + ? ["ots_getInternalOperations", txHash] : null, providerFetcher(provider) ); @@ -359,6 +317,26 @@ export const useInternalOperations = ( 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 = { type: string; depth: number; From 9683edf050feb44803e319ef6e0ae09ced8b3c10 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 17:29:54 -0300 Subject: [PATCH 04/11] Convert token metadata logic into SWR fetcher --- src/TokenTransferItem.tsx | 11 ++--- src/transaction/Details.tsx | 6 +-- src/types.ts | 1 - src/useErigonHooks.ts | 82 ++++++++++++++++++++++++------------- 4 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/TokenTransferItem.tsx b/src/TokenTransferItem.tsx index 0284a58..5fba3ca 100644 --- a/src/TokenTransferItem.tsx +++ b/src/TokenTransferItem.tsx @@ -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 = ({ - t, - tokenMeta, -}) => { +const TokenTransferItem: React.FC = ({ t }) => { const { provider } = useContext(RuntimeContext); const blockNumber = useBlockNumberContext(); const [quote, decimals] = useTokenUSDOracle(provider, blockNumber, t.token); + const tokenMeta = useTokenMetadata(provider, t.token); return (
diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index 6932f5c..bb41b04 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -285,11 +285,7 @@ const Details: React.FC = ({ txData }) => { {txData.tokenTransfers.length > 0 && ( {txData.tokenTransfers.map((t, i) => ( - + ))} )} diff --git a/src/types.ts b/src/types.ts index 44a134b..06d7c69 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,7 +37,6 @@ export type TransactionData = { to?: string; value: BigNumber; tokenTransfers: TokenTransfer[]; - tokenMetas: TokenMetas; type: number; maxFeePerGas?: BigNumber | undefined; maxPriorityFeePerGas?: BigNumber | undefined; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index a3620b7..61037fe 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -13,13 +13,13 @@ import { arrayify, hexDataSlice, isHexString } from "@ethersproject/bytes"; import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; import { - TokenMetas, TokenTransfer, TransactionData, InternalOperation, ProcessedTransaction, OperationType, ChecksummedAddress, + TokenMeta, } from "./types"; import erc20 from "./erc20.json"; @@ -216,40 +216,12 @@ export const useTxData = ( } } - // 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, @@ -665,3 +637,55 @@ export const useHasCode = ( } return data as boolean | undefined; }; + +const tokenMetadataFetcher = + (provider: JsonRpcProvider | undefined) => + async ( + _: "tokenmeta", + address: ChecksummedAddress + ): Promise => { + const erc20Contract = new Contract(address, erc20, provider); + 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; +}; From d09fe7a023c4b35400e502a35afeeb7fd0248015 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 17:40:04 -0300 Subject: [PATCH 05/11] Remove unnecessary check --- src/Transaction.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 905b5d8..13a901a 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -46,8 +46,7 @@ const Transaction: React.FC = () => { {txData.confirmedData?.blockNumber !== undefined && ( Logs - {txData && - ` (${txData.confirmedData?.logs?.length ?? 0})`} + {` (${txData.confirmedData?.logs?.length ?? 0})`} )} Trace From 682549fa4cd418a89be23e32db20c9912b44f8e6 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 18:11:32 -0300 Subject: [PATCH 06/11] Dont try to fetch 4bytes again in case of network errors --- src/use4Bytes.ts | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/use4Bytes.ts b/src/use4Bytes.ts index ddf8e2b..b61b076 100644 --- a/src/use4Bytes.ts +++ b/src/use4Bytes.ts @@ -53,23 +53,29 @@ const fourBytesFetcher = const fourBytes = key.slice(2); const signatureURL = fourBytesURL(assetsURLPrefix, fourBytes); - const res = await fetch(signatureURL); - if (!res.ok) { - console.warn(`Signature does not exist in 4bytes DB: ${fourBytes}`); + try { + const res = await fetch(signatureURL); + if (!res.ok) { + console.warn(`Signature does not exist in 4bytes DB: ${fourBytes}`); + return null; + } + + // Get only the first occurrence, for now ignore alternative param names + const sigs = await res.text(); + const sig = sigs.split(";")[0]; + const cut = sig.indexOf("("); + const method = sig.slice(0, cut); + + const entry: FourBytesEntry = { + name: method, + signature: sig, + }; + return entry; + } catch (err) { + // Network error or something wrong with URL config; + // silence and don't try it again return null; } - - // Get only the first occurrence, for now ignore alternative param names - const sigs = await res.text(); - const sig = sigs.split(";")[0]; - const cut = sig.indexOf("("); - const method = sig.slice(0, cut); - - const entry: FourBytesEntry = { - name: method, - signature: sig, - }; - return entry; }; /** From 18a7989a8b15b7e8abfa4f9a15bf5e0e7f15356e Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 19:01:30 -0300 Subject: [PATCH 07/11] Avoid rebuilding/parsing ABI the same contract objects every time --- src/api/address-resolver/ERCTokenResolver.ts | 6 ++-- src/api/address-resolver/UniswapV1Resolver.ts | 11 ++++--- src/api/address-resolver/UniswapV2Resolver.ts | 20 +++++++---- src/api/address-resolver/UniswapV3Resolver.ts | 20 +++++++---- src/useErigonHooks.ts | 9 ++++- src/usePriceOracle.ts | 33 +++++++++++++------ 6 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/api/address-resolver/ERCTokenResolver.ts b/src/api/address-resolver/ERCTokenResolver.ts index 49301bd..18ee2b6 100644 --- a/src/api/address-resolver/ERCTokenResolver.ts +++ b/src/api/address-resolver/ERCTokenResolver.ts @@ -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 { async resolveAddress( provider: BaseProvider, address: string ): Promise { - 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()) { diff --git a/src/api/address-resolver/UniswapV1Resolver.ts b/src/api/address-resolver/UniswapV1Resolver.ts index a8cb2e0..f3c33a5 100644 --- a/src/api/address-resolver/UniswapV1Resolver.ts +++ b/src/api/address-resolver/UniswapV1Resolver.ts @@ -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 { provider: BaseProvider, address: string ): Promise { - 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 diff --git a/src/api/address-resolver/UniswapV2Resolver.ts b/src/api/address-resolver/UniswapV2Resolver.ts index 761237c..e493ff0 100644 --- a/src/api/address-resolver/UniswapV2Resolver.ts +++ b/src/api/address-resolver/UniswapV2Resolver.ts @@ -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 { provider: BaseProvider, address: string ): Promise { - 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 diff --git a/src/api/address-resolver/UniswapV3Resolver.ts b/src/api/address-resolver/UniswapV3Resolver.ts index 835e823..c93d8df 100644 --- a/src/api/address-resolver/UniswapV3Resolver.ts +++ b/src/api/address-resolver/UniswapV3Resolver.ts @@ -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 { provider: BaseProvider, address: string ): Promise { - 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 diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 61037fe..1382715 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -10,6 +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 { AddressZero } from "@ethersproject/constants"; import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; import { @@ -638,13 +639,19 @@ 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 => { - const erc20Contract = new Contract(address, erc20, provider); + if (provider === undefined) { + return null; + } + + const erc20Contract = ERC20_PROTOTYPE.connect(provider).attach(address); try { const name = (await erc20Contract.name()) as string; if (!name.trim()) { diff --git a/src/usePriceOracle.ts b/src/usePriceOracle.ts index 1956a52..309aa9e 100644 --- a/src/usePriceOracle.ts +++ b/src/usePriceOracle.ts @@ -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 => 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,6 +82,8 @@ const ethUSDFetcherKey = (blockTag: BlockTag | undefined) => { return ["ethusd", blockTag]; }; +const ETH_USD_FEED_PROTOTYPE = new Contract(AddressZero, AggregatorV3Interface); + const ethUSDFetcher = ( provider: JsonRpcProvider | undefined @@ -84,7 +92,9 @@ const ethUSDFetcher = 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 priceData; }; @@ -120,6 +130,11 @@ const fastGasFetcherKey = (blockTag: BlockTag | undefined) => { return ["gasgwei", blockTag]; }; +const FAST_GAS_FEED_PROTOTYPE = new Contract( + AddressZero, + AggregatorV3Interface +); + const fastGasFetcher = ( provider: JsonRpcProvider | undefined @@ -128,10 +143,8 @@ const fastGasFetcher = if (provider?.network.chainId !== 1) { return undefined; } - const c = new Contract( - "fast-gas-gwei.data.eth", - AggregatorV3Interface, - provider + const c = FAST_GAS_FEED_PROTOTYPE.connect(provider).attach( + "fast-gas-gwei.data.eth" ); const priceData = await c.latestRoundData({ blockTag }); return priceData; From 34f812aed0632f826a5a1b957226d8507df0d9b5 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Tue, 23 Aug 2022 19:21:48 -0300 Subject: [PATCH 08/11] Extract token transfers logic --- src/transaction/Details.tsx | 14 ++++++++--- src/types.ts | 1 - src/useErigonHooks.ts | 49 ++++++++++++++++++++++--------------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/transaction/Details.tsx b/src/transaction/Details.tsx index bb41b04..e94b43e 100644 --- a/src/transaction/Details.tsx +++ b/src/transaction/Details.tsx @@ -43,7 +43,11 @@ import { useTransactionDescription as useSourcifyTransactionDescription, } from "../sourcify/useSourcify"; import { RuntimeContext } from "../useRuntime"; -import { useSendsToMiner, useTransactionError } from "../useErigonHooks"; +import { + useSendsToMiner, + useTokenTransfers, + useTransactionError, +} from "../useErigonHooks"; import { useChainInfo } from "../useChainInfo"; import { useETHUSDOracle } from "../usePriceOracle"; @@ -73,6 +77,8 @@ const Details: React.FC = ({ txData }) => { txData.confirmedData?.miner ); + const tokenTransfers = useTokenTransfers(txData); + const metadata = useSourcifyMetadata(txData?.to, provider?.network.chainId); const txDesc = useSourcifyTransactionDescription(metadata, txData); @@ -282,9 +288,9 @@ const Details: React.FC = ({ txData }) => { )} - {txData.tokenTransfers.length > 0 && ( - - {txData.tokenTransfers.map((t, i) => ( + {tokenTransfers && tokenTransfers.length > 0 && ( + + {tokenTransfers.map((t, i) => ( ))} diff --git a/src/types.ts b/src/types.ts index 06d7c69..e4ed03b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,7 +36,6 @@ export type TransactionData = { from: string; to?: string; value: BigNumber; - tokenTransfers: TokenTransfer[]; type: number; maxFeePerGas?: BigNumber | undefined; maxPriorityFeePerGas?: BigNumber | undefined; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 1382715..e1c0815 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -198,31 +198,11 @@ export const useTxData = ( 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), - }); - } - } - setTxData({ transactionHash: _response.hash, from: _response.from, to: _response.to, value: _response.value, - tokenTransfers, type: _response.type ?? 0, maxFeePerGas: _response.maxFeePerGas, maxPriorityFeePerGas: _response.maxPriorityFeePerGas, @@ -260,6 +240,35 @@ export const useTxData = ( return txData; }; +export const useTokenTransfers = ( + txData: TransactionData +): TokenTransfer[] | undefined => { + const transfers = useMemo(() => { + if (!txData.confirmedData) { + return undefined; + } + + const _transfers: TokenTransfer[] = []; + for (const l of txData.confirmedData.logs) { + if (l.topics.length !== 3) { + continue; + } + if (l.topics[0] !== TRANSFER_TOPIC) { + continue; + } + _transfers.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), + }); + } + return _transfers; + }, [txData]); + + return transfers; +}; + export const useInternalOperations = ( provider: JsonRpcProvider | undefined, txHash: string | undefined From e3a21bd4b20435a0baa992a71318cff24b78ea48 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Wed, 24 Aug 2022 04:34:41 -0300 Subject: [PATCH 09/11] Split block confirmation data into its own hook --- src/components/InternalSelfDestruct.tsx | 10 +++-- src/components/InternalTransfer.tsx | 13 +++---- src/components/TransactionAddress.tsx | 6 ++- src/transaction/Details.tsx | 49 +++++++++++-------------- src/transaction/RewardSplit.tsx | 16 +++++--- src/types.ts | 4 -- src/useErigonHooks.ts | 29 +++++++++------ 7 files changed, 67 insertions(+), 60 deletions(-) diff --git a/src/components/InternalSelfDestruct.tsx b/src/components/InternalSelfDestruct.tsx index f87233f..d3cd33a 100644 --- a/src/components/InternalSelfDestruct.tsx +++ b/src/components/InternalSelfDestruct.tsx @@ -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 = ({ 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 ( <> diff --git a/src/components/InternalTransfer.tsx b/src/components/InternalTransfer.tsx index 0faeb77..e1de103 100644 --- a/src/components/InternalTransfer.tsx +++ b/src/components/InternalTransfer.tsx @@ -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 = ({ 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 diff --git a/src/components/TransactionAddress.tsx b/src/components/TransactionAddress.tsx index 823bd50..6045cf5 100644 --- a/src/components/TransactionAddress.tsx +++ b/src/components/TransactionAddress.tsx @@ -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 = ({ 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 = ({ = ({ 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"; @@ -74,7 +75,7 @@ const Details: React.FC = ({ txData }) => { const [sendsEthToMiner, internalOps] = useSendsToMiner( provider, txData.confirmedData ? txData.transactionHash : undefined, - txData.confirmedData?.miner + block?.miner ); const tokenTransfers = useTokenTransfers(txData); @@ -222,22 +223,24 @@ const Details: React.FC = ({ txData }) => { confirmations={txData.confirmedData.confirmations} />
-
- - -
+ {block && ( +
+ + +
+ )}
- + {block && } )} @@ -366,18 +369,10 @@ const Details: React.FC = ({ txData }) => { )} - {txData.confirmedData && hasEIP1559 && ( + {block && hasEIP1559 && ( - {" "} - Gwei ( - {" "} - wei) + Gwei ( + wei) )} {txData.confirmedData && ( diff --git a/src/transaction/RewardSplit.tsx b/src/transaction/RewardSplit.tsx index 56dd0db..520ea0e 100644 --- a/src/transaction/RewardSplit.tsx +++ b/src/transaction/RewardSplit.tsx @@ -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 = ({ 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 = diff --git a/src/types.ts b/src/types.ts index e4ed03b..1379192 100644 --- a/src/types.ts +++ b/src/types.ts @@ -50,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; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index e1c0815..3fee30e 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -155,12 +155,15 @@ const blockDataFetcher = async ( 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 { data, error } = useSWRImmutable( - provider !== undefined ? [provider, blockNumberOrHash] : null, + provider !== undefined && blockNumberOrHash !== undefined + ? [provider, blockNumberOrHash] + : null, blockDataFetcher ); if (error) { @@ -169,6 +172,19 @@ export const useBlockData = ( return data; }; +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; +}; + export const useTxData = ( provider: JsonRpcProvider | undefined, txhash: string @@ -191,11 +207,6 @@ 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`; setTxData({ @@ -217,11 +228,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, From 66d6ccae10bbdc031904126052e3fd503dc9db4e Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Wed, 24 Aug 2022 04:38:15 -0300 Subject: [PATCH 10/11] Simplify syntax --- src/useErigonHooks.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 3fee30e..7de952a 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -255,22 +255,14 @@ export const useTokenTransfers = ( return undefined; } - const _transfers: TokenTransfer[] = []; - for (const l of txData.confirmedData.logs) { - if (l.topics.length !== 3) { - continue; - } - if (l.topics[0] !== TRANSFER_TOPIC) { - continue; - } - _transfers.push({ + 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), - }); - } - return _transfers; + })); }, [txData]); return transfers; From 32a93e211f487604197c0429b819f5fd509d0642 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Wed, 24 Aug 2022 04:44:18 -0300 Subject: [PATCH 11/11] Extract title into effect --- src/Transaction.tsx | 8 +++++++- src/useErigonHooks.ts | 2 -- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Transaction.tsx b/src/Transaction.tsx index 13a901a..4c2596a 100644 --- a/src/Transaction.tsx +++ b/src/Transaction.tsx @@ -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 ( diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index 7de952a..50663ce 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -207,8 +207,6 @@ export const useTxData = ( return; } - document.title = `Transaction ${_response.hash} | Otterscan`; - setTxData({ transactionHash: _response.hash, from: _response.from,