Merge branch 'feature/extract-address-resolvers' into develop
This commit is contained in:
commit
d41ab4330a
|
@ -26,7 +26,7 @@ import PendingResults from "./search/PendingResults";
|
||||||
import TransactionItem from "./search/TransactionItem";
|
import TransactionItem from "./search/TransactionItem";
|
||||||
import { SearchController } from "./search/search";
|
import { SearchController } from "./search/search";
|
||||||
import { RuntimeContext } from "./useRuntime";
|
import { RuntimeContext } from "./useRuntime";
|
||||||
import { useENSCache } from "./useReverseCache";
|
import { pageCollector, useResolvedAddresses } from "./useResolvedAddresses";
|
||||||
import { useFeeToggler } from "./search/useFeeToggler";
|
import { useFeeToggler } from "./search/useFeeToggler";
|
||||||
import { SelectionContext, useSelection } from "./useSelection";
|
import { SelectionContext, useSelection } from "./useSelection";
|
||||||
import { useMultipleETHUSDOracle } from "./usePriceOracle";
|
import { useMultipleETHUSDOracle } from "./usePriceOracle";
|
||||||
|
@ -165,7 +165,8 @@ const AddressTransactions: React.FC = () => {
|
||||||
}, [provider, checksummedAddress, params.direction, hash, controller]);
|
}, [provider, checksummedAddress, params.direction, hash, controller]);
|
||||||
|
|
||||||
const page = useMemo(() => controller?.getPage(), [controller]);
|
const page = useMemo(() => controller?.getPage(), [controller]);
|
||||||
const reverseCache = useENSCache(provider, page);
|
const addrCollector = useMemo(() => pageCollector(page), [page]);
|
||||||
|
const resolvedAddresses = useResolvedAddresses(provider, addrCollector);
|
||||||
|
|
||||||
const blockTags: BlockTag[] = useMemo(() => {
|
const blockTags: BlockTag[] = useMemo(() => {
|
||||||
if (!page) {
|
if (!page) {
|
||||||
|
@ -277,13 +278,13 @@ const AddressTransactions: React.FC = () => {
|
||||||
feeDisplay={feeDisplay}
|
feeDisplay={feeDisplay}
|
||||||
feeDisplayToggler={feeDisplayToggler}
|
feeDisplayToggler={feeDisplayToggler}
|
||||||
/>
|
/>
|
||||||
{controller ? (
|
{page ? (
|
||||||
<SelectionContext.Provider value={selectionCtx}>
|
<SelectionContext.Provider value={selectionCtx}>
|
||||||
{controller.getPage().map((tx) => (
|
{page.map((tx) => (
|
||||||
<TransactionItem
|
<TransactionItem
|
||||||
key={tx.hash}
|
key={tx.hash}
|
||||||
tx={tx}
|
tx={tx}
|
||||||
ensCache={reverseCache}
|
resolvedAddresses={resolvedAddresses}
|
||||||
selectedAddress={checksummedAddress}
|
selectedAddress={checksummedAddress}
|
||||||
feeDisplay={feeDisplay}
|
feeDisplay={feeDisplay}
|
||||||
priceMap={priceMap}
|
priceMap={priceMap}
|
||||||
|
|
|
@ -11,6 +11,10 @@ import { useInternalOperations, useTxData } from "./useErigonHooks";
|
||||||
import { useETHUSDOracle } from "./usePriceOracle";
|
import { useETHUSDOracle } from "./usePriceOracle";
|
||||||
import { useAppConfigContext } from "./useAppConfig";
|
import { useAppConfigContext } from "./useAppConfig";
|
||||||
import { useSourcify, useTransactionDescription } from "./useSourcify";
|
import { useSourcify, useTransactionDescription } from "./useSourcify";
|
||||||
|
import {
|
||||||
|
transactionDataCollector,
|
||||||
|
useResolvedAddresses,
|
||||||
|
} from "./useResolvedAddresses";
|
||||||
|
|
||||||
const Details = React.lazy(
|
const Details = React.lazy(
|
||||||
() =>
|
() =>
|
||||||
|
@ -36,6 +40,11 @@ const Transaction: React.FC = () => {
|
||||||
const { txhash } = params;
|
const { txhash } = params;
|
||||||
|
|
||||||
const txData = useTxData(provider, txhash);
|
const txData = useTxData(provider, txhash);
|
||||||
|
const addrCollector = useMemo(
|
||||||
|
() => transactionDataCollector(txData),
|
||||||
|
[txData]
|
||||||
|
);
|
||||||
|
const resolvedAddresses = useResolvedAddresses(provider, addrCollector);
|
||||||
|
|
||||||
const internalOps = useInternalOperations(provider, txData);
|
const internalOps = useInternalOperations(provider, txData);
|
||||||
const sendsEthToMiner = useMemo(() => {
|
const sendsEthToMiner = useMemo(() => {
|
||||||
|
@ -100,10 +109,15 @@ const Transaction: React.FC = () => {
|
||||||
internalOps={internalOps}
|
internalOps={internalOps}
|
||||||
sendsEthToMiner={sendsEthToMiner}
|
sendsEthToMiner={sendsEthToMiner}
|
||||||
ethUSDPrice={blockETHUSDPrice}
|
ethUSDPrice={blockETHUSDPrice}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/tx/:txhash/logs/" exact>
|
<Route path="/tx/:txhash/logs/" exact>
|
||||||
<Logs txData={txData} metadata={metadata} />
|
<Logs
|
||||||
|
txData={txData}
|
||||||
|
metadata={metadata}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
|
import { IAddressResolver } from "./address-resolver";
|
||||||
|
|
||||||
|
export class CompositeAddressResolver implements IAddressResolver {
|
||||||
|
private resolvers: IAddressResolver[] = [];
|
||||||
|
|
||||||
|
addResolver(resolver: IAddressResolver) {
|
||||||
|
this.resolvers.push(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveAddress(
|
||||||
|
provider: BaseProvider,
|
||||||
|
address: string
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
for (const r of this.resolvers) {
|
||||||
|
const name = r.resolveAddress(provider, address);
|
||||||
|
if (name !== undefined) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
// TODO: fallback to address itself
|
||||||
|
// return address;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
|
import { IAddressResolver } from "./address-resolver";
|
||||||
|
|
||||||
|
export class ENSAddressResolver implements IAddressResolver {
|
||||||
|
async resolveAddress(
|
||||||
|
provider: BaseProvider,
|
||||||
|
address: string
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const name = await provider.lookupAddress(address);
|
||||||
|
if (name === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
|
|
||||||
|
export interface IAddressResolver {
|
||||||
|
resolveAddress(
|
||||||
|
provider: BaseProvider,
|
||||||
|
address: string
|
||||||
|
): Promise<string | undefined>;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
|
import { IAddressResolver } from "./address-resolver";
|
||||||
|
import { CompositeAddressResolver } from "./CompositeAddressResolver";
|
||||||
|
import { ENSAddressResolver } from "./ENSAddressResolver";
|
||||||
|
|
||||||
|
export type ResolvedAddresses = Record<string, string>;
|
||||||
|
|
||||||
|
// Create and configure the main resolver
|
||||||
|
const _mainResolver = new CompositeAddressResolver();
|
||||||
|
_mainResolver.addResolver(new ENSAddressResolver());
|
||||||
|
|
||||||
|
export const mainResolver: IAddressResolver = _mainResolver;
|
||||||
|
|
||||||
|
export const batchPopulate = async (
|
||||||
|
provider: BaseProvider,
|
||||||
|
addresses: string[]
|
||||||
|
): Promise<ResolvedAddresses> => {
|
||||||
|
const solvers: Promise<string | undefined>[] = [];
|
||||||
|
for (const a of addresses) {
|
||||||
|
solvers.push(mainResolver.resolveAddress(provider, a));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await Promise.all(solvers);
|
||||||
|
const cache: ResolvedAddresses = {};
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
const r = results[i];
|
||||||
|
if (r === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cache[addresses[i]] = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
};
|
|
@ -8,7 +8,7 @@ import TransactionItem from "../search/TransactionItem";
|
||||||
import { useFeeToggler } from "../search/useFeeToggler";
|
import { useFeeToggler } from "../search/useFeeToggler";
|
||||||
import { RuntimeContext } from "../useRuntime";
|
import { RuntimeContext } from "../useRuntime";
|
||||||
import { SelectionContext, useSelection } from "../useSelection";
|
import { SelectionContext, useSelection } from "../useSelection";
|
||||||
import { useENSCache } from "../useReverseCache";
|
import { pageCollector, useResolvedAddresses } from "../useResolvedAddresses";
|
||||||
import { ProcessedTransaction } from "../types";
|
import { ProcessedTransaction } from "../types";
|
||||||
import { PAGE_SIZE } from "../params";
|
import { PAGE_SIZE } from "../params";
|
||||||
import { useMultipleETHUSDOracle } from "../usePriceOracle";
|
import { useMultipleETHUSDOracle } from "../usePriceOracle";
|
||||||
|
@ -29,7 +29,8 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
||||||
const selectionCtx = useSelection();
|
const selectionCtx = useSelection();
|
||||||
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
const [feeDisplay, feeDisplayToggler] = useFeeToggler();
|
||||||
const { provider } = useContext(RuntimeContext);
|
const { provider } = useContext(RuntimeContext);
|
||||||
const reverseCache = useENSCache(provider, page);
|
const addrCollector = useMemo(() => pageCollector(page), [page]);
|
||||||
|
const resolvedAddresses = useResolvedAddresses(provider, addrCollector);
|
||||||
const blockTags = useMemo(() => [blockTag], [blockTag]);
|
const blockTags = useMemo(() => [blockTag], [blockTag]);
|
||||||
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
|
const priceMap = useMultipleETHUSDOracle(provider, blockTags);
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ const BlockTransactionResults: React.FC<BlockTransactionResultsProps> = ({
|
||||||
<TransactionItem
|
<TransactionItem
|
||||||
key={tx.hash}
|
key={tx.hash}
|
||||||
tx={tx}
|
tx={tx}
|
||||||
ensCache={reverseCache}
|
resolvedAddresses={resolvedAddresses}
|
||||||
feeDisplay={feeDisplay}
|
feeDisplay={feeDisplay}
|
||||||
priceMap={priceMap}
|
priceMap={priceMap}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,8 +6,8 @@ type AddressProps = {
|
||||||
|
|
||||||
const Address: React.FC<AddressProps> = ({ address }) => (
|
const Address: React.FC<AddressProps> = ({ address }) => (
|
||||||
<span className="font-address text-gray-400 truncate" title={address}>
|
<span className="font-address text-gray-400 truncate" title={address}>
|
||||||
<span className="truncate">{address}</span>
|
{address}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default React.memo(Address);
|
export default Address;
|
||||||
|
|
|
@ -17,11 +17,10 @@ const AddressLink: React.FC<AddressLinkProps> = ({
|
||||||
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
||||||
} font-address truncate`}
|
} font-address truncate`}
|
||||||
to={`/address/${address}`}
|
to={`/address/${address}`}
|
||||||
|
title={text ?? address}
|
||||||
>
|
>
|
||||||
<span className="truncate" title={text ?? address}>
|
{text ?? address}
|
||||||
{text ?? address}
|
|
||||||
</span>
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default React.memo(AddressLink);
|
export default AddressLink;
|
||||||
|
|
|
@ -3,49 +3,53 @@ import Address from "./Address";
|
||||||
import AddressLink from "./AddressLink";
|
import AddressLink from "./AddressLink";
|
||||||
import ENSName from "./ENSName";
|
import ENSName from "./ENSName";
|
||||||
import ENSNameLink from "./ENSNameLink";
|
import ENSNameLink from "./ENSNameLink";
|
||||||
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type AddressOrENSNameProps = {
|
type AddressOrENSNameProps = {
|
||||||
address: string;
|
address: string;
|
||||||
ensName?: string;
|
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
dontOverrideColors?: boolean;
|
dontOverrideColors?: boolean;
|
||||||
|
resolvedAddresses?: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddressOrENSName: React.FC<AddressOrENSNameProps> = ({
|
const AddressOrENSName: React.FC<AddressOrENSNameProps> = ({
|
||||||
address,
|
address,
|
||||||
ensName,
|
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
text,
|
text,
|
||||||
dontOverrideColors,
|
dontOverrideColors,
|
||||||
}) => (
|
resolvedAddresses,
|
||||||
<>
|
}) => {
|
||||||
{address === selectedAddress ? (
|
const name = resolvedAddresses?.[address];
|
||||||
<>
|
return (
|
||||||
{ensName ? (
|
<>
|
||||||
<ENSName name={ensName} address={address} />
|
{address === selectedAddress ? (
|
||||||
) : (
|
<>
|
||||||
<Address address={address} />
|
{name ? (
|
||||||
)}
|
<ENSName name={name} address={address} />
|
||||||
</>
|
) : (
|
||||||
) : (
|
<Address address={address} />
|
||||||
<>
|
)}
|
||||||
{ensName ? (
|
</>
|
||||||
<ENSNameLink
|
) : (
|
||||||
name={ensName}
|
<>
|
||||||
address={address}
|
{name ? (
|
||||||
dontOverrideColors={dontOverrideColors}
|
<ENSNameLink
|
||||||
/>
|
name={name}
|
||||||
) : (
|
address={address}
|
||||||
<AddressLink
|
dontOverrideColors={dontOverrideColors}
|
||||||
address={address}
|
/>
|
||||||
text={text}
|
) : (
|
||||||
dontOverrideColors={dontOverrideColors}
|
<AddressLink
|
||||||
/>
|
address={address}
|
||||||
)}
|
text={text}
|
||||||
</>
|
dontOverrideColors={dontOverrideColors}
|
||||||
)}
|
/>
|
||||||
</>
|
)}
|
||||||
);
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default React.memo(AddressOrENSName);
|
export default AddressOrENSName;
|
||||||
|
|
|
@ -8,10 +8,10 @@ import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
|
||||||
import TokenLogo from "./TokenLogo";
|
import TokenLogo from "./TokenLogo";
|
||||||
import AddressOrENSName from "./AddressOrENSName";
|
import AddressOrENSName from "./AddressOrENSName";
|
||||||
import { AddressContext, TokenMeta, ZERO_ADDRESS } from "../types";
|
import { AddressContext, TokenMeta, ZERO_ADDRESS } from "../types";
|
||||||
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type DecoratedAddressLinkProps = {
|
type DecoratedAddressLinkProps = {
|
||||||
address: string;
|
address: string;
|
||||||
ensName?: string;
|
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
addressCtx?: AddressContext;
|
addressCtx?: AddressContext;
|
||||||
|
@ -21,11 +21,11 @@ type DecoratedAddressLinkProps = {
|
||||||
txFrom?: boolean;
|
txFrom?: boolean;
|
||||||
txTo?: boolean;
|
txTo?: boolean;
|
||||||
tokenMeta?: TokenMeta;
|
tokenMeta?: TokenMeta;
|
||||||
|
resolvedAddresses?: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DecoratedAddresssLink: React.FC<DecoratedAddressLinkProps> = ({
|
const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
|
||||||
address,
|
address,
|
||||||
ensName,
|
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
text,
|
text,
|
||||||
addressCtx,
|
addressCtx,
|
||||||
|
@ -35,6 +35,7 @@ const DecoratedAddresssLink: React.FC<DecoratedAddressLinkProps> = ({
|
||||||
txFrom,
|
txFrom,
|
||||||
txTo,
|
txTo,
|
||||||
tokenMeta,
|
tokenMeta,
|
||||||
|
resolvedAddresses,
|
||||||
}) => {
|
}) => {
|
||||||
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;
|
||||||
|
@ -81,13 +82,13 @@ const DecoratedAddresssLink: React.FC<DecoratedAddressLinkProps> = ({
|
||||||
)}
|
)}
|
||||||
<AddressOrENSName
|
<AddressOrENSName
|
||||||
address={address}
|
address={address}
|
||||||
ensName={ensName}
|
|
||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
text={text}
|
text={text}
|
||||||
dontOverrideColors={mint || burn}
|
dontOverrideColors={mint || burn}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(DecoratedAddresssLink);
|
export default React.memo(DecoratedAddressLink);
|
||||||
|
|
|
@ -22,4 +22,4 @@ const ENSName: React.FC<ENSNameProps> = ({ name, address }) => (
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default React.memo(ENSName);
|
export default ENSName;
|
||||||
|
|
|
@ -31,4 +31,4 @@ const ENSNameLink: React.FC<ENSNameLinkProps> = ({
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default React.memo(ENSNameLink);
|
export default ENSNameLink;
|
||||||
|
|
|
@ -14,14 +14,15 @@ import TransactionDirection, {
|
||||||
Flags,
|
Flags,
|
||||||
} from "../components/TransactionDirection";
|
} from "../components/TransactionDirection";
|
||||||
import TransactionValue from "../components/TransactionValue";
|
import TransactionValue from "../components/TransactionValue";
|
||||||
import { ENSReverseCache, ProcessedTransaction } from "../types";
|
import { 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";
|
||||||
|
|
||||||
type TransactionItemProps = {
|
type TransactionItemProps = {
|
||||||
tx: ProcessedTransaction;
|
tx: ProcessedTransaction;
|
||||||
ensCache?: ENSReverseCache;
|
resolvedAddresses?: ResolvedAddresses;
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
feeDisplay: FeeDisplay;
|
feeDisplay: FeeDisplay;
|
||||||
priceMap: Record<BlockTag, BigNumber>;
|
priceMap: Record<BlockTag, BigNumber>;
|
||||||
|
@ -29,7 +30,7 @@ type TransactionItemProps = {
|
||||||
|
|
||||||
const TransactionItem: React.FC<TransactionItemProps> = ({
|
const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
tx,
|
tx,
|
||||||
ensCache,
|
resolvedAddresses,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
feeDisplay,
|
feeDisplay,
|
||||||
priceMap,
|
priceMap,
|
||||||
|
@ -50,12 +51,6 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensFrom = ensCache && tx.from && ensCache[tx.from];
|
|
||||||
const ensTo = ensCache && tx.to && ensCache[tx.to];
|
|
||||||
const ensCreated =
|
|
||||||
ensCache &&
|
|
||||||
tx.createdContractAddress &&
|
|
||||||
ensCache[tx.createdContractAddress];
|
|
||||||
const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction;
|
const flash = tx.gasPrice.isZero() && tx.internalMinerInteraction;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -87,9 +82,9 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
<AddressHighlighter address={tx.from}>
|
<AddressHighlighter address={tx.from}>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={tx.from}
|
address={tx.from}
|
||||||
ensName={ensFrom}
|
|
||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
miner={tx.miner === tx.from}
|
miner={tx.miner === tx.from}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
)}
|
)}
|
||||||
|
@ -107,18 +102,18 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
<AddressHighlighter address={tx.to}>
|
<AddressHighlighter address={tx.to}>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={tx.to}
|
address={tx.to}
|
||||||
ensName={ensTo}
|
|
||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
miner={tx.miner === tx.to}
|
miner={tx.miner === tx.to}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
) : (
|
) : (
|
||||||
<AddressHighlighter address={tx.createdContractAddress!}>
|
<AddressHighlighter address={tx.createdContractAddress!}>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={tx.createdContractAddress!}
|
address={tx.createdContractAddress!}
|
||||||
ensName={ensCreated}
|
|
||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
creation
|
creation
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
)}
|
)}
|
||||||
|
@ -144,4 +139,4 @@ const TransactionItem: React.FC<TransactionItemProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(TransactionItem);
|
export default TransactionItem;
|
||||||
|
|
|
@ -38,6 +38,7 @@ import ModeTab from "../components/ModeTab";
|
||||||
import DecodedParamsTable from "./decoder/DecodedParamsTable";
|
import DecodedParamsTable from "./decoder/DecodedParamsTable";
|
||||||
import { rawInputTo4Bytes, use4Bytes } from "../use4Bytes";
|
import { rawInputTo4Bytes, use4Bytes } from "../use4Bytes";
|
||||||
import { DevDoc, UserDoc } from "../useSourcify";
|
import { DevDoc, UserDoc } from "../useSourcify";
|
||||||
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type DetailsProps = {
|
type DetailsProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
|
@ -47,6 +48,7 @@ type DetailsProps = {
|
||||||
internalOps?: InternalOperation[];
|
internalOps?: InternalOperation[];
|
||||||
sendsEthToMiner: boolean;
|
sendsEthToMiner: boolean;
|
||||||
ethUSDPrice: BigNumber | undefined;
|
ethUSDPrice: BigNumber | undefined;
|
||||||
|
resolvedAddresses: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Details: React.FC<DetailsProps> = ({
|
const Details: React.FC<DetailsProps> = ({
|
||||||
|
@ -57,6 +59,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
internalOps,
|
internalOps,
|
||||||
sendsEthToMiner,
|
sendsEthToMiner,
|
||||||
ethUSDPrice,
|
ethUSDPrice,
|
||||||
|
resolvedAddresses,
|
||||||
}) => {
|
}) => {
|
||||||
const hasEIP1559 =
|
const hasEIP1559 =
|
||||||
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
|
txData.confirmedData?.blockBaseFeePerGas !== undefined &&
|
||||||
|
@ -154,6 +157,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
address={txData.from}
|
address={txData.from}
|
||||||
miner={txData.from === txData.confirmedData?.miner}
|
miner={txData.from === txData.confirmedData?.miner}
|
||||||
txFrom
|
txFrom
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
<Copy value={txData.from} />
|
<Copy value={txData.from} />
|
||||||
|
@ -171,6 +175,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
address={txData.to}
|
address={txData.to}
|
||||||
miner={txData.to === txData.confirmedData?.miner}
|
miner={txData.to === txData.confirmedData?.miner}
|
||||||
txTo
|
txTo
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
<Copy value={txData.to} />
|
<Copy value={txData.to} />
|
||||||
|
@ -188,6 +193,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
address={txData.confirmedData.createdContractAddress!}
|
address={txData.confirmedData.createdContractAddress!}
|
||||||
creation
|
creation
|
||||||
txTo
|
txTo
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
<Copy value={txData.confirmedData.createdContractAddress!} />
|
<Copy value={txData.confirmedData.createdContractAddress!} />
|
||||||
|
|
|
@ -10,14 +10,21 @@ import DecodedParamsTable from "./decoder/DecodedParamsTable";
|
||||||
import DecodedLogSignature from "./decoder/DecodedLogSignature";
|
import DecodedLogSignature from "./decoder/DecodedLogSignature";
|
||||||
import { TransactionData } from "../types";
|
import { TransactionData } from "../types";
|
||||||
import { useTopic0 } from "../useTopic0";
|
import { useTopic0 } from "../useTopic0";
|
||||||
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type LogEntryProps = {
|
type LogEntryProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
log: Log;
|
log: Log;
|
||||||
logDesc: LogDescription | null | undefined;
|
logDesc: LogDescription | null | undefined;
|
||||||
|
resolvedAddresses: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LogEntry: React.FC<LogEntryProps> = ({ txData, log, logDesc }) => {
|
const LogEntry: React.FC<LogEntryProps> = ({
|
||||||
|
txData,
|
||||||
|
log,
|
||||||
|
logDesc,
|
||||||
|
resolvedAddresses,
|
||||||
|
}) => {
|
||||||
const rawTopic0 = log.topics[0];
|
const rawTopic0 = log.topics[0];
|
||||||
const topic0 = useTopic0(rawTopic0);
|
const topic0 = useTopic0(rawTopic0);
|
||||||
|
|
||||||
|
@ -62,6 +69,7 @@ const LogEntry: React.FC<LogEntryProps> = ({ txData, log, logDesc }) => {
|
||||||
miner={log.address === txData.confirmedData?.miner}
|
miner={log.address === txData.confirmedData?.miner}
|
||||||
txFrom={log.address === txData.from}
|
txFrom={log.address === txData.from}
|
||||||
txTo={log.address === txData.to}
|
txTo={log.address === txData.to}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
<Copy value={log.address} />
|
<Copy value={log.address} />
|
||||||
|
|
|
@ -5,13 +5,15 @@ import LogEntry from "./LogEntry";
|
||||||
import { TransactionData } from "../types";
|
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";
|
||||||
|
|
||||||
type LogsProps = {
|
type LogsProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
metadata: Metadata | null | undefined;
|
metadata: Metadata | null | undefined;
|
||||||
|
resolvedAddresses: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Logs: React.FC<LogsProps> = ({ txData, metadata }) => {
|
const Logs: React.FC<LogsProps> = ({ txData, metadata, resolvedAddresses }) => {
|
||||||
const baseMetadatas = useMemo((): Record<string, Metadata | null> => {
|
const baseMetadatas = useMemo((): Record<string, Metadata | null> => {
|
||||||
if (!txData.to || metadata === undefined) {
|
if (!txData.to || metadata === undefined) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -70,6 +72,7 @@ const Logs: React.FC<LogsProps> = ({ txData, metadata }) => {
|
||||||
txData={txData}
|
txData={txData}
|
||||||
log={l}
|
log={l}
|
||||||
logDesc={logDescs?.[i]}
|
logDesc={logDescs?.[i]}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -32,10 +32,6 @@ export type TransactionChunk = {
|
||||||
lastPage: boolean;
|
lastPage: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ENSReverseCache = {
|
|
||||||
[address: string]: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TransactionData = {
|
export type TransactionData = {
|
||||||
transactionHash: string;
|
transactionHash: string;
|
||||||
from: string;
|
from: string;
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { JsonRpcProvider } from "@ethersproject/providers";
|
||||||
|
import { ProcessedTransaction, TransactionData } from "./types";
|
||||||
|
import { batchPopulate, ResolvedAddresses } from "./api/address-resolver";
|
||||||
|
|
||||||
|
export type AddressCollector = () => string[];
|
||||||
|
|
||||||
|
export const pageCollector =
|
||||||
|
(page: ProcessedTransaction[] | undefined): AddressCollector =>
|
||||||
|
() => {
|
||||||
|
if (!page) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueAddresses = new Set<string>();
|
||||||
|
for (const tx of page) {
|
||||||
|
if (tx.from) {
|
||||||
|
uniqueAddresses.add(tx.from);
|
||||||
|
}
|
||||||
|
if (tx.to) {
|
||||||
|
uniqueAddresses.add(tx.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(uniqueAddresses);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transactionDataCollector =
|
||||||
|
(txData: TransactionData | null | undefined): AddressCollector =>
|
||||||
|
() => {
|
||||||
|
if (!txData) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueAddresses = new Set<string>();
|
||||||
|
|
||||||
|
// 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 useResolvedAddresses = (
|
||||||
|
provider: JsonRpcProvider | undefined,
|
||||||
|
addrCollector: AddressCollector
|
||||||
|
) => {
|
||||||
|
const [names, setNames] = useState<ResolvedAddresses>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const populate = async () => {
|
||||||
|
const _addresses = addrCollector();
|
||||||
|
const _names = await batchPopulate(provider, _addresses);
|
||||||
|
setNames(_names);
|
||||||
|
};
|
||||||
|
populate();
|
||||||
|
}, [provider, addrCollector]);
|
||||||
|
|
||||||
|
return names;
|
||||||
|
};
|
|
@ -1,48 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { JsonRpcProvider } from "@ethersproject/providers";
|
|
||||||
import { ENSReverseCache, ProcessedTransaction } from "./types";
|
|
||||||
|
|
||||||
export const useENSCache = (
|
|
||||||
provider?: JsonRpcProvider,
|
|
||||||
page?: ProcessedTransaction[]
|
|
||||||
) => {
|
|
||||||
const [reverseCache, setReverseCache] = useState<ENSReverseCache>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!provider || !page) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addrSet = new Set<string>();
|
|
||||||
for (const tx of page) {
|
|
||||||
if (tx.from) {
|
|
||||||
addrSet.add(tx.from);
|
|
||||||
}
|
|
||||||
if (tx.to) {
|
|
||||||
addrSet.add(tx.to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const addresses = Array.from(addrSet);
|
|
||||||
|
|
||||||
const reverseResolve = async () => {
|
|
||||||
const solvers: Promise<string | null>[] = [];
|
|
||||||
for (const a of addresses) {
|
|
||||||
solvers.push(provider.lookupAddress(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await Promise.all(solvers);
|
|
||||||
const cache: ENSReverseCache = {};
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
|
||||||
const r = results[i];
|
|
||||||
if (r === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cache[addresses[i]] = r;
|
|
||||||
}
|
|
||||||
setReverseCache(cache);
|
|
||||||
};
|
|
||||||
reverseResolve();
|
|
||||||
}, [provider, page]);
|
|
||||||
|
|
||||||
return reverseCache;
|
|
||||||
};
|
|
Loading…
Reference in New Issue