import React, { useState, useEffect, useMemo, useContext } from "react"; import { useParams, useLocation, useHistory, Switch, Route, } from "react-router-dom"; import { BlockTag } from "@ethersproject/abstract-provider"; import { getAddress, isAddress } from "@ethersproject/address"; import { Tab } from "@headlessui/react"; import queryString from "query-string"; import Blockies from "react-blockies"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons/faQuestionCircle"; import StandardFrame from "./StandardFrame"; import StandardSubtitle from "./StandardSubtitle"; import Copy from "./components/Copy"; import ContentFrame from "./ContentFrame"; import NavTab from "./components/NavTab"; import Contracts from "./address/Contracts"; import UndefinedPageControl from "./search/UndefinedPageControl"; import ResultHeader from "./search/ResultHeader"; import PendingResults from "./search/PendingResults"; import TransactionItem from "./search/TransactionItem"; import { SearchController } from "./search/search"; import { RuntimeContext } from "./useRuntime"; import { pageCollector, useResolvedAddresses } from "./useResolvedAddresses"; import { useFeeToggler } from "./search/useFeeToggler"; import { SelectionContext, useSelection } from "./useSelection"; import { useMultipleETHUSDOracle } from "./usePriceOracle"; import { useAppConfigContext } from "./useAppConfig"; import { useSourcify } from "./useSourcify"; import SourcifyLogo from "./sourcify.svg"; type BlockParams = { addressOrName: string; direction?: string; }; type PageParams = { p?: number; }; const AddressTransactions: React.FC = () => { const { provider } = useContext(RuntimeContext); const params = useParams(); const location = useLocation(); const history = useHistory(); const qs = queryString.parse(location.search); let hash: string | undefined; if (qs.h) { hash = qs.h as string; } const [checksummedAddress, setChecksummedAddress] = useState(); const [isENS, setENS] = useState(); const [error, setError] = useState(); // If it looks like it is an ENS name, try to resolve it useEffect(() => { if (isAddress(params.addressOrName)) { setENS(false); setError(false); // Normalize to checksummed address const _checksummedAddress = getAddress(params.addressOrName); if (_checksummedAddress !== params.addressOrName) { // Request came with a non-checksummed address; fix the URL history.replace( `/address/${_checksummedAddress}${ params.direction ? "/" + params.direction : "" }${location.search}` ); } setChecksummedAddress(_checksummedAddress); return; } if (!provider) { return; } const resolveName = async () => { const resolvedAddress = await provider.resolveName(params.addressOrName); if (resolvedAddress !== null) { setENS(true); setError(false); setChecksummedAddress(resolvedAddress); } else { setENS(false); setError(true); setChecksummedAddress(undefined); } }; resolveName(); }, [ provider, params.addressOrName, history, params.direction, location.search, ]); const [controller, setController] = useState(); useEffect(() => { if (!provider || !checksummedAddress) { return; } const readFirstPage = async () => { const _controller = await SearchController.firstPage( provider, checksummedAddress ); setController(_controller); }; const readMiddlePage = async (next: boolean) => { const _controller = await SearchController.middlePage( provider, checksummedAddress, hash!, next ); setController(_controller); }; const readLastPage = async () => { const _controller = await SearchController.lastPage( provider, checksummedAddress ); setController(_controller); }; const prevPage = async () => { const _controller = await controller!.prevPage(provider, hash!); setController(_controller); }; const nextPage = async () => { const _controller = await controller!.nextPage(provider, hash!); setController(_controller); }; // Page load from scratch if (params.direction === "first" || params.direction === undefined) { if (!controller?.isFirst || controller.address !== checksummedAddress) { readFirstPage(); } } else if (params.direction === "prev") { if (controller && controller.address === checksummedAddress) { prevPage(); } else { readMiddlePage(false); } } else if (params.direction === "next") { if (controller && controller.address === checksummedAddress) { nextPage(); } else { readMiddlePage(true); } } else if (params.direction === "last") { if (!controller?.isLast || controller.address !== checksummedAddress) { readLastPage(); } } }, [provider, checksummedAddress, params.direction, hash, controller]); const page = useMemo(() => controller?.getPage(), [controller]); const addrCollector = useMemo(() => pageCollector(page), [page]); const resolvedAddresses = useResolvedAddresses(provider, addrCollector); 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`; const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const selectionCtx = useSelection(); const { sourcifySource } = useAppConfigContext(); const rawMetadata = useSourcify( checksummedAddress, provider?.network.chainId, sourcifySource ); return ( {error ? ( "{params.addressOrName}" is not an ETH address or ENS name. ) : ( checksummedAddress && ( <>
Address {checksummedAddress} {isENS && ( ENS: {params.addressOrName} )}
Overview Contract {rawMetadata === undefined ? ( ) : rawMetadata === null ? ( ) : ( Sourcify logo )}
{page === undefined ? ( <>Waiting for search results... ) : ( <>{page.length} transactions on this page )}
{controller ? ( {controller.getPage().map((tx) => ( ))}
{page === undefined ? ( <>Waiting for search results... ) : ( <>{page.length} transactions on this page )}
) : ( )}
) )}
); }; export default React.memo(AddressTransactions);