import { useState, useEffect, useRef, useContext } from "react"; import { BaseProvider, JsonRpcProvider } from "@ethersproject/providers"; import { getAddress, isAddress } from "@ethersproject/address"; import useSWR from "swr"; import { batchPopulate, mainResolver, ResolvedAddresses, } from "./api/address-resolver"; import { SelectedResolvedName } from "./api/address-resolver/CompositeAddressResolver"; import { TraceGroup } from "./useErigonHooks"; import { RuntimeContext } from "./useRuntime"; import { ChecksummedAddress, ProcessedTransaction, TransactionData, } from "./types"; export const useAddressOrENSFromURL = ( addressOrName: string, urlFixer: (address: ChecksummedAddress) => void ): [ ChecksummedAddress | undefined, boolean | undefined, boolean | undefined ] => { const { provider } = useContext(RuntimeContext); const [checksummedAddress, setChecksummedAddress] = useState< ChecksummedAddress | undefined >(); const [isENS, setENS] = useState(); const [error, setError] = useState(); // If it looks like it is an ENS name, try to resolve it useEffect(() => { // Reset setENS(false); setError(false); setChecksummedAddress(undefined); // TODO: handle and offer fallback to bad checksummed addresses if (isAddress(addressOrName)) { // Normalize to checksummed address const _checksummedAddress = getAddress(addressOrName); if (_checksummedAddress !== addressOrName) { // Request came with a non-checksummed address; fix the URL urlFixer(_checksummedAddress); return; } setENS(false); setError(false); setChecksummedAddress(_checksummedAddress); return; } if (!provider) { return; } const resolveName = async () => { const resolvedAddress = await provider.resolveName(addressOrName); if (resolvedAddress !== null) { setENS(true); setError(false); setChecksummedAddress(resolvedAddress); } else { setENS(false); setError(true); setChecksummedAddress(undefined); } }; resolveName(); }, [provider, addressOrName, urlFixer]); return [checksummedAddress, isENS, error]; }; export type AddressCollector = () => string[]; export const pageCollector = (page: ProcessedTransaction[] | undefined): AddressCollector => () => { if (!page) { return []; } const uniqueAddresses = new Set(); for (const tx of page) { if (tx.from) { uniqueAddresses.add(tx.from); } if (tx.to) { uniqueAddresses.add(tx.to); } if (tx.createdContractAddress) { uniqueAddresses.add(tx.createdContractAddress); } } return Array.from(uniqueAddresses); }; export const transactionDataCollector = (txData: TransactionData | null | undefined): AddressCollector => () => { if (!txData) { return []; } const uniqueAddresses = new Set(); // Standard fields uniqueAddresses.add(txData.from); if (txData.to) { uniqueAddresses.add(txData.to); } if (txData.confirmedData?.createdContractAddress) { uniqueAddresses.add(txData.confirmedData?.createdContractAddress); } // Dig token transfers for (const t of txData.tokenTransfers) { uniqueAddresses.add(t.from); uniqueAddresses.add(t.to); uniqueAddresses.add(t.token); } // Dig log addresses if (txData.confirmedData) { for (const l of txData.confirmedData.logs) { uniqueAddresses.add(l.address); // TODO: find a way to dig over decoded address log attributes } } return Array.from(uniqueAddresses); }; export const tracesCollector = (traces: TraceGroup[] | undefined): AddressCollector => () => { if (traces === undefined) { return []; } const uniqueAddresses = new Set(); let searchTraces = [...traces]; while (searchTraces.length > 0) { const nextSearch: TraceGroup[] = []; for (const g of searchTraces) { uniqueAddresses.add(g.from); uniqueAddresses.add(g.to); if (g.children) { nextSearch.push(...g.children); } } searchTraces = nextSearch; } return Array.from(uniqueAddresses); }; export const useResolvedAddresses = ( provider: JsonRpcProvider | undefined, addrCollector: AddressCollector ) => { const [names, setNames] = useState(); const ref = useRef(); useEffect(() => { ref.current = names; }); useEffect( () => { if (!provider) { return; } const populate = async () => { const _addresses = addrCollector(); const _names = await batchPopulate(provider, _addresses, ref.current); setNames(_names); }; populate(); }, // DON'T put names variables in dependency array; this is intentional; useRef [provider, addrCollector] ); return names; }; export const useResolvedAddress = ( provider: BaseProvider | undefined, address: ChecksummedAddress ): SelectedResolvedName | undefined => { const fetcher = async ( key: string ): Promise | undefined> => { if (!provider) { return undefined; } return mainResolver.resolveAddress(provider, address); }; const { data, error } = useSWR(address, fetcher); if (error) { return undefined; } return data; };