Merge branch 'develop' into feature/trace-tx

This commit is contained in:
Willian Mitsuda 2021-11-11 05:27:36 -03:00
commit 56c5c420fb
11 changed files with 154 additions and 48 deletions

View File

@ -31,7 +31,8 @@ import { useFeeToggler } from "./search/useFeeToggler";
import { SelectionContext, useSelection } from "./useSelection"; import { SelectionContext, useSelection } from "./useSelection";
import { useMultipleETHUSDOracle } from "./usePriceOracle"; import { useMultipleETHUSDOracle } from "./usePriceOracle";
import { useAppConfigContext } from "./useAppConfig"; import { useAppConfigContext } from "./useAppConfig";
import { useSourcify } from "./useSourcify"; import { useMultipleMetadata } from "./useSourcify";
import { ChecksummedAddress } from "./types";
import SourcifyLogo from "./sourcify.svg"; import SourcifyLogo from "./sourcify.svg";
type BlockParams = { type BlockParams = {
@ -181,12 +182,31 @@ const AddressTransactions: React.FC = () => {
const [feeDisplay, feeDisplayToggler] = useFeeToggler(); const [feeDisplay, feeDisplayToggler] = useFeeToggler();
const selectionCtx = useSelection(); const selectionCtx = useSelection();
const addresses = useMemo(() => {
const _addresses: ChecksummedAddress[] = [];
if (checksummedAddress) {
_addresses.push(checksummedAddress);
}
if (page) {
for (const t of page) {
if (t.to) {
_addresses.push(t.to);
}
}
}
return _addresses;
}, [checksummedAddress, page]);
const { sourcifySource } = useAppConfigContext(); const { sourcifySource } = useAppConfigContext();
const rawMetadata = useSourcify( const metadatas = useMultipleMetadata(
checksummedAddress, undefined,
addresses,
provider?.network.chainId, provider?.network.chainId,
sourcifySource sourcifySource
); );
const addressMetadata =
checksummedAddress !== undefined
? metadatas[checksummedAddress]
: undefined;
return ( return (
<StandardFrame> <StandardFrame>
@ -224,18 +244,18 @@ const AddressTransactions: React.FC = () => {
<NavTab href={`/address/${checksummedAddress}/contract`}> <NavTab href={`/address/${checksummedAddress}/contract`}>
<span <span
className={`flex items-baseline space-x-2 ${ className={`flex items-baseline space-x-2 ${
rawMetadata === undefined ? "italic opacity-50" : "" addressMetadata === undefined ? "italic opacity-50" : ""
}`} }`}
> >
<span>Contract</span> <span>Contract</span>
{rawMetadata === undefined ? ( {addressMetadata === undefined ? (
<span className="self-center"> <span className="self-center">
<FontAwesomeIcon <FontAwesomeIcon
className="animate-spin" className="animate-spin"
icon={faCircleNotch} icon={faCircleNotch}
/> />
</span> </span>
) : rawMetadata === null ? ( ) : addressMetadata === null ? (
<span className="self-center text-red-500"> <span className="self-center text-red-500">
<FontAwesomeIcon icon={faQuestionCircle} /> <FontAwesomeIcon icon={faQuestionCircle} />
</span> </span>
@ -288,6 +308,7 @@ const AddressTransactions: React.FC = () => {
selectedAddress={checksummedAddress} selectedAddress={checksummedAddress}
feeDisplay={feeDisplay} feeDisplay={feeDisplay}
priceMap={priceMap} priceMap={priceMap}
metadatas={metadatas}
/> />
))} ))}
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">
@ -316,7 +337,7 @@ const AddressTransactions: React.FC = () => {
<Route path="/address/:addressOrName/contract" exact> <Route path="/address/:addressOrName/contract" exact>
<Contracts <Contracts
checksummedAddress={checksummedAddress} checksummedAddress={checksummedAddress}
rawMetadata={rawMetadata} rawMetadata={addressMetadata}
/> />
</Route> </Route>
</Switch> </Switch>

View File

@ -9,9 +9,11 @@ import { useFeeToggler } from "../search/useFeeToggler";
import { RuntimeContext } from "../useRuntime"; import { RuntimeContext } from "../useRuntime";
import { SelectionContext, useSelection } from "../useSelection"; import { SelectionContext, useSelection } from "../useSelection";
import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses"; import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses";
import { ProcessedTransaction } from "../types"; import { ChecksummedAddress, ProcessedTransaction } from "../types";
import { PAGE_SIZE } from "../params"; import { PAGE_SIZE } from "../params";
import { useMultipleETHUSDOracle } from "../usePriceOracle"; import { useMultipleETHUSDOracle } from "../usePriceOracle";
import { useAppConfigContext } from "../useAppConfig";
import { useMultipleMetadata } from "../useSourcify";
type BlockTransactionResultsProps = { type BlockTransactionResultsProps = {
blockTag: BlockTag; blockTag: BlockTag;
@ -34,6 +36,21 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
const blockTags = useMemo(() => [blockTag], [blockTag]); const blockTags = useMemo(() => [blockTag], [blockTag]);
const priceMap = useMultipleETHUSDOracle(provider, blockTags); const priceMap = useMultipleETHUSDOracle(provider, blockTags);
const addresses = useMemo((): ChecksummedAddress[] => {
if (!page) {
return [];
}
return page.map((t) => t.to).filter((to): to is string => to !== undefined);
}, [page]);
const { sourcifySource } = useAppConfigContext();
const metadatas = useMultipleMetadata(
undefined,
addresses,
provider?.network.chainId,
sourcifySource
);
return ( return (
<ContentFrame> <ContentFrame>
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">
@ -63,6 +80,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
feeDisplay={feeDisplay} feeDisplay={feeDisplay}
priceMap={priceMap} priceMap={priceMap}
metadatas={metadatas}
/> />
))} ))}
<div className="flex justify-between items-baseline py-3"> <div className="flex justify-between items-baseline py-3">

View File

@ -8,6 +8,8 @@ import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
import AddressOrENSName from "./AddressOrENSName"; import AddressOrENSName from "./AddressOrENSName";
import { AddressContext, ZERO_ADDRESS } from "../types"; import { AddressContext, ZERO_ADDRESS } from "../types";
import { ResolvedAddresses } from "../api/address-resolver"; import { ResolvedAddresses } from "../api/address-resolver";
import { Metadata } from "../useSourcify";
import SourcifyLogo from "../sourcify.svg";
type DecoratedAddressLinkProps = { type DecoratedAddressLinkProps = {
address: string; address: string;
@ -19,6 +21,7 @@ type DecoratedAddressLinkProps = {
txFrom?: boolean; txFrom?: boolean;
txTo?: boolean; txTo?: boolean;
resolvedAddresses?: ResolvedAddresses | undefined; resolvedAddresses?: ResolvedAddresses | undefined;
metadata?: Metadata | null | undefined;
}; };
const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
@ -31,6 +34,7 @@ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
txFrom, txFrom,
txTo, txTo,
resolvedAddresses, resolvedAddresses,
metadata,
}) => { }) => {
const mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS; const mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS;
const burn = addressCtx === AddressContext.TO && address === ZERO_ADDRESS; const burn = addressCtx === AddressContext.TO && address === ZERO_ADDRESS;
@ -70,6 +74,17 @@ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
<FontAwesomeIcon icon={faCoins} size="1x" /> <FontAwesomeIcon icon={faCoins} size="1x" />
</span> </span>
)} )}
{metadata && (
<span className="self-center flex-shrink-0 flex items-center">
<img
src={SourcifyLogo}
alt="Sourcify logo"
title="Verified by Sourcify"
width={16}
height={16}
/>
</span>
)}
<AddressOrENSName <AddressOrENSName
address={address} address={address}
selectedAddress={selectedAddress} selectedAddress={selectedAddress}

View File

@ -4,17 +4,20 @@ import DecoratedAddressLink from "./DecoratedAddressLink";
import { ResolvedAddresses } from "../api/address-resolver"; import { ResolvedAddresses } from "../api/address-resolver";
import { useSelectedTransaction } from "../useSelectedTransaction"; import { useSelectedTransaction } from "../useSelectedTransaction";
import { AddressContext } from "../types"; import { AddressContext } from "../types";
import { Metadata } from "../useSourcify";
type TransactionAddressProps = { type TransactionAddressProps = {
address: string; address: string;
addressCtx?: AddressContext | undefined; addressCtx?: AddressContext | undefined;
resolvedAddresses: ResolvedAddresses | undefined; resolvedAddresses: ResolvedAddresses | undefined;
metadata?: Metadata | null | undefined;
}; };
const TransactionAddress: React.FC<TransactionAddressProps> = ({ const TransactionAddress: React.FC<TransactionAddressProps> = ({
address, address,
addressCtx, addressCtx,
resolvedAddresses, resolvedAddresses,
metadata,
}) => { }) => {
const txData = useSelectedTransaction(); const txData = useSelectedTransaction();
// TODO: push down creation coloring logic into DecoratedAddressLink // TODO: push down creation coloring logic into DecoratedAddressLink
@ -30,6 +33,7 @@ const TransactionAddress: React.FC<TransactionAddressProps> = ({
txTo={address === txData?.to || creation} txTo={address === txData?.to || creation}
creation={creation} creation={creation}
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadata={metadata}
/> />
</AddressHighlighter> </AddressHighlighter>
); );

View File

@ -14,11 +14,12 @@ import TransactionDirection, {
Flags, Flags,
} from "../components/TransactionDirection"; } from "../components/TransactionDirection";
import TransactionValue from "../components/TransactionValue"; import TransactionValue from "../components/TransactionValue";
import { ProcessedTransaction } from "../types"; import { ChecksummedAddress, ProcessedTransaction } from "../types";
import { FeeDisplay } from "./useFeeToggler"; import { FeeDisplay } from "./useFeeToggler";
import { formatValue } from "../components/formatter"; import { formatValue } from "../components/formatter";
import ETH2USDValue from "../components/ETH2USDValue"; import ETH2USDValue from "../components/ETH2USDValue";
import { ResolvedAddresses } from "../api/address-resolver"; import { ResolvedAddresses } from "../api/address-resolver";
import { Metadata } from "../useSourcify";
type TransactionItemProps = { type TransactionItemProps = {
tx: ProcessedTransaction; tx: ProcessedTransaction;
@ -26,6 +27,7 @@ type TransactionItemProps = {
selectedAddress?: string; selectedAddress?: string;
feeDisplay: FeeDisplay; feeDisplay: FeeDisplay;
priceMap: Record<BlockTag, BigNumber>; priceMap: Record<BlockTag, BigNumber>;
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
}; };
const TransactionItem: React.FC<TransactionItemProps> = ({ const TransactionItem: React.FC<TransactionItemProps> = ({
@ -34,6 +36,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
selectedAddress, selectedAddress,
feeDisplay, feeDisplay,
priceMap, priceMap,
metadatas,
}) => { }) => {
let direction: Direction | undefined; let direction: Direction | undefined;
if (selectedAddress) { if (selectedAddress) {
@ -105,6 +108,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
miner={tx.miner === tx.to} miner={tx.miner === tx.to}
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadata={metadatas[tx.to]}
/> />
</AddressHighlighter> </AddressHighlighter>
) : ( ) : (
@ -114,6 +118,7 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
creation creation
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadata={metadatas[tx.createdContractAddress!]}
/> />
</AddressHighlighter> </AddressHighlighter>
)} )}

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useContext, useMemo } from "react";
import { TransactionDescription } from "@ethersproject/abi"; import { TransactionDescription } from "@ethersproject/abi";
import { BigNumber } from "@ethersproject/bignumber"; import { BigNumber } from "@ethersproject/bignumber";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -22,7 +22,11 @@ import USDValue from "../components/USDValue";
import FormattedBalance from "../components/FormattedBalance"; import FormattedBalance from "../components/FormattedBalance";
import ETH2USDValue from "../components/ETH2USDValue"; import ETH2USDValue from "../components/ETH2USDValue";
import TokenTransferItem from "../TokenTransferItem"; import TokenTransferItem from "../TokenTransferItem";
import { TransactionData, InternalOperation } from "../types"; import {
TransactionData,
InternalOperation,
ChecksummedAddress,
} from "../types";
import PercentageBar from "../components/PercentageBar"; import PercentageBar from "../components/PercentageBar";
import ExternalLink from "../components/ExternalLink"; import ExternalLink from "../components/ExternalLink";
import RelativePosition from "../components/RelativePosition"; import RelativePosition from "../components/RelativePosition";
@ -33,8 +37,10 @@ import {
use4Bytes, use4Bytes,
useTransactionDescription, useTransactionDescription,
} from "../use4Bytes"; } from "../use4Bytes";
import { DevDoc, UserDoc } from "../useSourcify"; import { DevDoc, useMultipleMetadata, UserDoc } from "../useSourcify";
import { ResolvedAddresses } from "../api/address-resolver"; import { ResolvedAddresses } from "../api/address-resolver";
import { RuntimeContext } from "../useRuntime";
import { useAppConfigContext } from "../useAppConfig";
type DetailsProps = { type DetailsProps = {
txData: TransactionData; txData: TransactionData;
@ -73,6 +79,25 @@ const Details: React.FC<DetailsProps> = ({
const userMethod = txDesc ? userDoc?.methods[txDesc.signature] : undefined; const userMethod = txDesc ? userDoc?.methods[txDesc.signature] : undefined;
const devMethod = txDesc ? devDoc?.methods[txDesc.signature] : undefined; const devMethod = txDesc ? devDoc?.methods[txDesc.signature] : undefined;
const { provider } = useContext(RuntimeContext);
const addresses = useMemo(() => {
const _addresses: ChecksummedAddress[] = [];
if (txData.to) {
_addresses.push(txData.to);
}
if (txData.confirmedData?.createdContractAddress) {
_addresses.push(txData.confirmedData.createdContractAddress);
}
return _addresses;
}, [txData]);
const { sourcifySource } = useAppConfigContext();
const metadatas = useMultipleMetadata(
undefined,
addresses,
provider?.network.chainId,
sourcifySource
);
return ( return (
<ContentFrame tabs> <ContentFrame tabs>
<InfoRow title="Transaction Hash"> <InfoRow title="Transaction Hash">
@ -148,6 +173,7 @@ const Details: React.FC<DetailsProps> = ({
<TransactionAddress <TransactionAddress
address={txData.to} address={txData.to}
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadata={metadatas?.[txData.to]}
/> />
<Copy value={txData.to} /> <Copy value={txData.to} />
</div> </div>
@ -160,6 +186,9 @@ const Details: React.FC<DetailsProps> = ({
<TransactionAddress <TransactionAddress
address={txData.confirmedData?.createdContractAddress!} address={txData.confirmedData?.createdContractAddress!}
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadata={
metadatas?.[txData.confirmedData?.createdContractAddress!]
}
/> />
<Copy value={txData.confirmedData.createdContractAddress!} /> <Copy value={txData.confirmedData.createdContractAddress!} />
</div> </div>

View File

@ -9,17 +9,21 @@ import DecodedParamsTable from "./decoder/DecodedParamsTable";
import DecodedLogSignature from "./decoder/DecodedLogSignature"; import DecodedLogSignature from "./decoder/DecodedLogSignature";
import { useTopic0 } from "../useTopic0"; import { useTopic0 } from "../useTopic0";
import { ResolvedAddresses } from "../api/address-resolver"; import { ResolvedAddresses } from "../api/address-resolver";
import { ChecksummedAddress } from "../types";
import { Metadata } from "../useSourcify";
type LogEntryProps = { type LogEntryProps = {
log: Log; log: Log;
logDesc: LogDescription | null | undefined; logDesc: LogDescription | null | undefined;
resolvedAddresses: ResolvedAddresses | undefined; resolvedAddresses: ResolvedAddresses | undefined;
metadatas: Record<ChecksummedAddress, Metadata | null | undefined>;
}; };
const LogEntry: React.FC<LogEntryProps> = ({ const LogEntry: React.FC<LogEntryProps> = ({
log, log,
logDesc, logDesc,
resolvedAddresses, resolvedAddresses,
metadatas,
}) => { }) => {
const rawTopic0 = log.topics[0]; const rawTopic0 = log.topics[0];
const topic0 = useTopic0(rawTopic0); const topic0 = useTopic0(rawTopic0);
@ -62,6 +66,7 @@ const LogEntry: React.FC<LogEntryProps> = ({
<TransactionAddress <TransactionAddress
address={log.address} address={log.address}
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadata={metadatas[log.address]}
/> />
<Copy value={log.address} /> <Copy value={log.address} />
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react"; import React, { useContext, useMemo } from "react";
import { Interface } from "@ethersproject/abi"; import { Interface } from "@ethersproject/abi";
import ContentFrame from "../ContentFrame"; import ContentFrame from "../ContentFrame";
import LogEntry from "./LogEntry"; import LogEntry from "./LogEntry";
@ -6,6 +6,7 @@ import { TransactionData } from "../types";
import { useAppConfigContext } from "../useAppConfig"; import { useAppConfigContext } from "../useAppConfig";
import { Metadata, useMultipleMetadata } from "../useSourcify"; import { Metadata, useMultipleMetadata } from "../useSourcify";
import { ResolvedAddresses } from "../api/address-resolver"; import { ResolvedAddresses } from "../api/address-resolver";
import { RuntimeContext } from "../useRuntime";
type LogsProps = { type LogsProps = {
txData: TransactionData; txData: TransactionData;
@ -24,15 +25,16 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => {
return md; return md;
}, [txData.to, metadata]); }, [txData.to, metadata]);
const { sourcifySource } = useAppConfigContext();
const logAddresses = useMemo( const logAddresses = useMemo(
() => txData.confirmedData?.logs.map((l) => l.address) ?? [], () => txData.confirmedData?.logs.map((l) => l.address) ?? [],
[txData] [txData]
); );
const { provider } = useContext(RuntimeContext);
const { sourcifySource } = useAppConfigContext();
const metadatas = useMultipleMetadata( const metadatas = useMultipleMetadata(
baseMetadatas, baseMetadatas,
logAddresses, logAddresses,
1, provider?.network.chainId,
sourcifySource sourcifySource
); );
const logDescs = useMemo(() => { const logDescs = useMemo(() => {
@ -72,6 +74,7 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => {
log={l} log={l}
logDesc={logDescs?.[i]} logDesc={logDescs?.[i]}
resolvedAddresses={resolvedAddresses} resolvedAddresses={resolvedAddresses}
metadatas={metadatas}
/> />
))} ))}
</> </>

View File

@ -67,6 +67,9 @@ export type ConfirmedTransactionData = {
// The VOID... // The VOID...
export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
// TODO: replace all occurrences of plain string
export type ChecksummedAddress = string;
export enum AddressContext { export enum AddressContext {
FROM, FROM,
TO, TO,

View File

@ -1,4 +1,5 @@
import { BlockTag } from "@ethersproject/abstract-provider"; import { BlockTag } from "@ethersproject/abstract-provider";
import { ChecksummedAddress } from "./types";
export const fourBytesURL = ( export const fourBytesURL = (
assetsURLPrefix: string, assetsURLPrefix: string,
@ -42,23 +43,23 @@ const resolveSourcifySource = (source: SourcifySource) => {
}; };
export const sourcifyMetadata = ( export const sourcifyMetadata = (
checksummedAddress: string, address: ChecksummedAddress,
networkId: number, chainId: number,
source: SourcifySource source: SourcifySource
) => ) =>
`${resolveSourcifySource( `${resolveSourcifySource(
source source
)}/contracts/full_match/${networkId}/${checksummedAddress}/metadata.json`; )}/contracts/full_match/${chainId}/${address}/metadata.json`;
export const sourcifySourceFile = ( export const sourcifySourceFile = (
checksummedAddress: string, address: ChecksummedAddress,
networkId: number, chainId: number,
filepath: string, filepath: string,
source: SourcifySource source: SourcifySource
) => ) =>
`${resolveSourcifySource( `${resolveSourcifySource(
source source
)}/contracts/full_match/${networkId}/${checksummedAddress}/sources/${filepath}`; )}/contracts/full_match/${chainId}/${address}/sources/${filepath}`;
export const openInRemixURL = (checksummedAddress: string, networkId: number) => export const openInRemixURL = (checksummedAddress: string, networkId: number) =>
`https://remix.ethereum.org/#call=source-verification//fetchAndSave//${checksummedAddress}//${networkId}`; `https://remix.ethereum.org/#call=source-verification//fetchAndSave//${checksummedAddress}//${networkId}`;

View File

@ -1,6 +1,6 @@
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from "react";
import { Interface } from "@ethersproject/abi"; import { Interface } from "@ethersproject/abi";
import { TransactionData } from "./types"; import { ChecksummedAddress, TransactionData } from "./types";
import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "./url"; import { sourcifyMetadata, SourcifySource, sourcifySourceFile } from "./url";
export type UserMethod = { export type UserMethod = {
@ -65,24 +65,19 @@ export type Metadata = {
}; };
}; };
export const fetchSourcifyMetadata = async ( const fetchSourcifyMetadata = async (
checksummedAddress: string, address: ChecksummedAddress,
chainId: number, chainId: number,
source: SourcifySource, source: SourcifySource,
abortController: AbortController abortController: AbortController
): Promise<Metadata | null> => { ): Promise<Metadata | null> => {
try { try {
const contractMetadataURL = sourcifyMetadata( const metadataURL = sourcifyMetadata(address, chainId, source);
checksummedAddress, const result = await fetch(metadataURL, {
chainId,
source
);
const result = await fetch(contractMetadataURL, {
signal: abortController.signal, signal: abortController.signal,
}); });
if (result.ok) { if (result.ok) {
const _metadata = await result.json(); return await result.json();
return _metadata;
} }
return null; return null;
@ -92,15 +87,16 @@ export const fetchSourcifyMetadata = async (
} }
}; };
// TODO: replace every occurrence with the multiple version one
export const useSourcify = ( export const useSourcify = (
checksummedAddress: string | undefined, address: ChecksummedAddress | undefined,
chainId: number | undefined, chainId: number | undefined,
source: SourcifySource source: SourcifySource
): Metadata | null | undefined => { ): Metadata | null | undefined => {
const [rawMetadata, setRawMetadata] = useState<Metadata | null | undefined>(); const [rawMetadata, setRawMetadata] = useState<Metadata | null | undefined>();
useEffect(() => { useEffect(() => {
if (!checksummedAddress || chainId === undefined) { if (!address || chainId === undefined) {
return; return;
} }
setRawMetadata(undefined); setRawMetadata(undefined);
@ -108,7 +104,7 @@ export const useSourcify = (
const abortController = new AbortController(); const abortController = new AbortController();
const fetchMetadata = async () => { const fetchMetadata = async () => {
const _metadata = await fetchSourcifyMetadata( const _metadata = await fetchSourcifyMetadata(
checksummedAddress, address,
chainId, chainId,
source, source,
abortController abortController
@ -120,47 +116,53 @@ export const useSourcify = (
return () => { return () => {
abortController.abort(); abortController.abort();
}; };
}, [checksummedAddress, chainId, source]); }, [address, chainId, source]);
return rawMetadata; return rawMetadata;
}; };
export const useMultipleMetadata = ( export const useMultipleMetadata = (
baseMetadatas: Record<string, Metadata | null>, baseMetadatas: Record<string, Metadata | null> | undefined,
checksummedAddress: (string | undefined)[], addresses: (ChecksummedAddress | undefined)[],
chainId: number | undefined, chainId: number | undefined,
source: SourcifySource source: SourcifySource
): Record<string, Metadata | null | undefined> => { ): Record<ChecksummedAddress, Metadata | null | undefined> => {
const [rawMetadata, setRawMetadata] = useState< const [rawMetadata, setRawMetadata] = useState<
Record<string, Metadata | null | undefined> Record<string, Metadata | null | undefined>
>({}); >({});
useEffect(() => { useEffect(() => {
if (!checksummedAddress || chainId === undefined) { if (!addresses || chainId === undefined) {
return; return;
} }
setRawMetadata({}); setRawMetadata({});
const abortController = new AbortController(); const abortController = new AbortController();
const fetchMetadata = async (addresses: string[]) => { const fetchMetadata = async (dedupedAddresses: string[]) => {
const promises: Promise<Metadata | null>[] = []; const promises: Promise<Metadata | null>[] = [];
for (const addr of addresses) { for (const address of dedupedAddresses) {
promises.push( promises.push(
fetchSourcifyMetadata(addr, chainId, source, abortController) fetchSourcifyMetadata(address, chainId, source, abortController)
); );
} }
const results = await Promise.all(promises); const results = await Promise.all(promises);
const metadatas: Record<string, Metadata | null> = { ...baseMetadatas }; if (abortController.signal.aborted) {
return;
}
const metadatas: Record<string, Metadata | null> = baseMetadatas
? { ...baseMetadatas }
: {};
for (let i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
metadatas[addresses[i]] = results[i]; metadatas[dedupedAddresses[i]] = results[i];
} }
setRawMetadata(metadatas); setRawMetadata(metadatas);
}; };
const deduped = new Set( const deduped = new Set(
checksummedAddress.filter( addresses.filter(
(a): a is string => a !== undefined && baseMetadatas[a] === undefined (a): a is ChecksummedAddress =>
a !== undefined && baseMetadatas?.[a] === undefined
) )
); );
fetchMetadata(Array.from(deduped)); fetchMetadata(Array.from(deduped));
@ -168,7 +170,7 @@ export const useMultipleMetadata = (
return () => { return () => {
abortController.abort(); abortController.abort();
}; };
}, [baseMetadatas, checksummedAddress, chainId, source]); }, [baseMetadatas, addresses, chainId, source]);
return rawMetadata; return rawMetadata;
}; };