Merge branch 'feature/erc20-address-resolver' into develop
This commit is contained in:
commit
f2c75ca6e2
|
@ -11,11 +11,13 @@ import {
|
||||||
TokenTransfer,
|
TokenTransfer,
|
||||||
TransactionData,
|
TransactionData,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { ResolvedAddresses } from "./api/address-resolver";
|
||||||
|
|
||||||
type TokenTransferItemProps = {
|
type TokenTransferItemProps = {
|
||||||
t: TokenTransfer;
|
t: TokenTransfer;
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
tokenMeta?: TokenMeta | undefined;
|
tokenMeta?: TokenMeta | undefined;
|
||||||
|
resolvedAddresses: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: handle partial
|
// TODO: handle partial
|
||||||
|
@ -23,6 +25,7 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
||||||
t,
|
t,
|
||||||
txData,
|
txData,
|
||||||
tokenMeta,
|
tokenMeta,
|
||||||
|
resolvedAddresses,
|
||||||
}) => (
|
}) => (
|
||||||
<div className="flex items-baseline space-x-2 px-2 py-1 truncate hover:bg-gray-100">
|
<div className="flex items-baseline space-x-2 px-2 py-1 truncate hover:bg-gray-100">
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500">
|
||||||
|
@ -64,10 +67,7 @@ const TokenTransferItem: React.FC<TokenTransferItemProps> = ({
|
||||||
<AddressHighlighter address={t.token}>
|
<AddressHighlighter address={t.token}>
|
||||||
<DecoratedAddressLink
|
<DecoratedAddressLink
|
||||||
address={t.token}
|
address={t.token}
|
||||||
text={
|
resolvedAddresses={resolvedAddresses}
|
||||||
tokenMeta ? `${tokenMeta.name} (${tokenMeta.symbol})` : undefined
|
|
||||||
}
|
|
||||||
tokenMeta={tokenMeta}
|
|
||||||
/>
|
/>
|
||||||
</AddressHighlighter>
|
</AddressHighlighter>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
import { BaseProvider } from "@ethersproject/providers";
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
import { IAddressResolver } from "./address-resolver";
|
import { IAddressResolver } from "./address-resolver";
|
||||||
|
|
||||||
export class CompositeAddressResolver implements IAddressResolver {
|
export type SelectedResolvedName<T> = [IAddressResolver<T>, T] | null;
|
||||||
private resolvers: IAddressResolver[] = [];
|
|
||||||
|
|
||||||
addResolver(resolver: IAddressResolver) {
|
export class CompositeAddressResolver<T = any>
|
||||||
|
implements IAddressResolver<SelectedResolvedName<T>>
|
||||||
|
{
|
||||||
|
private resolvers: IAddressResolver<T>[] = [];
|
||||||
|
|
||||||
|
addResolver(resolver: IAddressResolver<T>) {
|
||||||
this.resolvers.push(resolver);
|
this.resolvers.push(resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveAddress(
|
async resolveAddress(
|
||||||
provider: BaseProvider,
|
provider: BaseProvider,
|
||||||
address: string
|
address: string
|
||||||
): Promise<string | undefined> {
|
): Promise<SelectedResolvedName<T> | undefined> {
|
||||||
for (const r of this.resolvers) {
|
for (const r of this.resolvers) {
|
||||||
const name = r.resolveAddress(provider, address);
|
const resolvedAddress = await r.resolveAddress(provider, address);
|
||||||
if (name !== undefined) {
|
if (resolvedAddress !== undefined) {
|
||||||
return name;
|
return [r, resolvedAddress];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return null;
|
||||||
// TODO: fallback to address itself
|
|
||||||
// return address;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { BaseProvider } from "@ethersproject/providers";
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
import { IAddressResolver } from "./address-resolver";
|
import { IAddressResolver } from "./address-resolver";
|
||||||
|
|
||||||
export class ENSAddressResolver implements IAddressResolver {
|
export class ENSAddressResolver implements IAddressResolver<string> {
|
||||||
async resolveAddress(
|
async resolveAddress(
|
||||||
provider: BaseProvider,
|
provider: BaseProvider,
|
||||||
address: string
|
address: string
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
|
import { Contract } from "@ethersproject/contracts";
|
||||||
|
import { IAddressResolver } from "./address-resolver";
|
||||||
|
import erc20 from "../../erc20.json";
|
||||||
|
import { TokenMeta } from "../../types";
|
||||||
|
|
||||||
|
export class ERCTokenResolver implements IAddressResolver<TokenMeta> {
|
||||||
|
async resolveAddress(
|
||||||
|
provider: BaseProvider,
|
||||||
|
address: string
|
||||||
|
): Promise<TokenMeta | undefined> {
|
||||||
|
const erc20Contract = new Contract(address, erc20, provider);
|
||||||
|
try {
|
||||||
|
const [name, symbol, decimals] = await Promise.all([
|
||||||
|
erc20Contract.name(),
|
||||||
|
erc20Contract.symbol(),
|
||||||
|
erc20Contract.decimals(),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
symbol,
|
||||||
|
decimals,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore on purpose; this indicates the probe failed and the address
|
||||||
|
// is not a token
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,16 @@
|
||||||
|
import React from "react";
|
||||||
import { BaseProvider } from "@ethersproject/providers";
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
|
|
||||||
export interface IAddressResolver {
|
export interface IAddressResolver<T> {
|
||||||
resolveAddress(
|
resolveAddress(
|
||||||
provider: BaseProvider,
|
provider: BaseProvider,
|
||||||
address: string
|
address: string
|
||||||
): Promise<string | undefined>;
|
): Promise<T | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResolvedAddressRenderer<T> = (
|
||||||
|
address: string,
|
||||||
|
resolvedAddress: T,
|
||||||
|
linkable: boolean,
|
||||||
|
dontOverrideColors: boolean
|
||||||
|
) => React.ReactElement;
|
||||||
|
|
|
@ -1,34 +1,57 @@
|
||||||
import { BaseProvider } from "@ethersproject/providers";
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
import { IAddressResolver } from "./address-resolver";
|
import { ensRenderer } from "../../components/ENSName";
|
||||||
import { CompositeAddressResolver } from "./CompositeAddressResolver";
|
import { tokenRenderer } from "../../components/TokenName";
|
||||||
|
import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver";
|
||||||
|
import {
|
||||||
|
CompositeAddressResolver,
|
||||||
|
SelectedResolvedName,
|
||||||
|
} from "./CompositeAddressResolver";
|
||||||
import { ENSAddressResolver } from "./ENSAddressResolver";
|
import { ENSAddressResolver } from "./ENSAddressResolver";
|
||||||
|
import { ERCTokenResolver } from "./ERCTokenResolver";
|
||||||
|
|
||||||
export type ResolvedAddresses = Record<string, string>;
|
export type ResolvedAddresses = Record<string, SelectedResolvedName<any>>;
|
||||||
|
|
||||||
// Create and configure the main resolver
|
// Create and configure the main resolver
|
||||||
|
export const ensResolver = new ENSAddressResolver();
|
||||||
|
export const ercTokenResolver = new ERCTokenResolver();
|
||||||
|
|
||||||
const _mainResolver = new CompositeAddressResolver();
|
const _mainResolver = new CompositeAddressResolver();
|
||||||
_mainResolver.addResolver(new ENSAddressResolver());
|
_mainResolver.addResolver(ensResolver);
|
||||||
|
_mainResolver.addResolver(ercTokenResolver);
|
||||||
|
|
||||||
export const mainResolver: IAddressResolver = _mainResolver;
|
export const mainResolver: IAddressResolver<SelectedResolvedName<any>> =
|
||||||
|
_mainResolver;
|
||||||
|
|
||||||
|
export const resolverRendererRegistry = new Map<
|
||||||
|
IAddressResolver<any>,
|
||||||
|
ResolvedAddressRenderer<any>
|
||||||
|
>();
|
||||||
|
resolverRendererRegistry.set(ensResolver, ensRenderer);
|
||||||
|
resolverRendererRegistry.set(ercTokenResolver, tokenRenderer);
|
||||||
|
|
||||||
|
// TODO: implement progressive resolving
|
||||||
export const batchPopulate = async (
|
export const batchPopulate = async (
|
||||||
provider: BaseProvider,
|
provider: BaseProvider,
|
||||||
addresses: string[]
|
addresses: string[],
|
||||||
|
currentMap: ResolvedAddresses | undefined
|
||||||
): Promise<ResolvedAddresses> => {
|
): Promise<ResolvedAddresses> => {
|
||||||
const solvers: Promise<string | undefined>[] = [];
|
const solvers: Promise<SelectedResolvedName<any> | undefined>[] = [];
|
||||||
for (const a of addresses) {
|
const unresolvedAddresses = addresses.filter(
|
||||||
|
(a) => currentMap?.[a] === undefined
|
||||||
|
);
|
||||||
|
for (const a of unresolvedAddresses) {
|
||||||
solvers.push(mainResolver.resolveAddress(provider, a));
|
solvers.push(mainResolver.resolveAddress(provider, a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resultMap: ResolvedAddresses = currentMap ? { ...currentMap } : {};
|
||||||
const results = await Promise.all(solvers);
|
const results = await Promise.all(solvers);
|
||||||
const cache: ResolvedAddresses = {};
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
const r = results[i];
|
const r = results[i];
|
||||||
if (r === undefined) {
|
if (r === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
cache[addresses[i]] = r;
|
resultMap[unresolvedAddresses[i]] = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache;
|
return resultMap;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type AddressProps = {
|
|
||||||
address: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Address: React.FC<AddressProps> = ({ address }) => (
|
|
||||||
<span className="font-address text-gray-400 truncate" title={address}>
|
|
||||||
{address}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Address;
|
|
|
@ -1,26 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { NavLink } from "react-router-dom";
|
|
||||||
|
|
||||||
type AddressLinkProps = {
|
|
||||||
address: string;
|
|
||||||
text?: string;
|
|
||||||
dontOverrideColors?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddressLink: React.FC<AddressLinkProps> = ({
|
|
||||||
address,
|
|
||||||
text,
|
|
||||||
dontOverrideColors,
|
|
||||||
}) => (
|
|
||||||
<NavLink
|
|
||||||
className={`${
|
|
||||||
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
|
||||||
} font-address truncate`}
|
|
||||||
to={`/address/${address}`}
|
|
||||||
title={text ?? address}
|
|
||||||
>
|
|
||||||
{text ?? address}
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default AddressLink;
|
|
|
@ -1,14 +1,13 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Address from "./Address";
|
import {
|
||||||
import AddressLink from "./AddressLink";
|
ResolvedAddresses,
|
||||||
import ENSName from "./ENSName";
|
resolverRendererRegistry,
|
||||||
import ENSNameLink from "./ENSNameLink";
|
} from "../api/address-resolver";
|
||||||
import { ResolvedAddresses } from "../api/address-resolver";
|
import PlainAddress from "./PlainAddress";
|
||||||
|
|
||||||
type AddressOrENSNameProps = {
|
type AddressOrENSNameProps = {
|
||||||
address: string;
|
address: string;
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
text?: string;
|
|
||||||
dontOverrideColors?: boolean;
|
dontOverrideColors?: boolean;
|
||||||
resolvedAddresses?: ResolvedAddresses | undefined;
|
resolvedAddresses?: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
@ -16,40 +15,35 @@ type AddressOrENSNameProps = {
|
||||||
const AddressOrENSName: React.FC<AddressOrENSNameProps> = ({
|
const AddressOrENSName: React.FC<AddressOrENSNameProps> = ({
|
||||||
address,
|
address,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
text,
|
|
||||||
dontOverrideColors,
|
dontOverrideColors,
|
||||||
resolvedAddresses,
|
resolvedAddresses,
|
||||||
}) => {
|
}) => {
|
||||||
const name = resolvedAddresses?.[address];
|
const resolvedAddress = resolvedAddresses?.[address];
|
||||||
return (
|
const linkable = address !== selectedAddress;
|
||||||
<>
|
|
||||||
{address === selectedAddress ? (
|
if (!resolvedAddress) {
|
||||||
<>
|
return (
|
||||||
{name ? (
|
<PlainAddress
|
||||||
<ENSName name={name} address={address} />
|
address={address}
|
||||||
) : (
|
linkable={linkable}
|
||||||
<Address address={address} />
|
dontOverrideColors={dontOverrideColors}
|
||||||
)}
|
/>
|
||||||
</>
|
);
|
||||||
) : (
|
}
|
||||||
<>
|
|
||||||
{name ? (
|
const [resolver, resolvedName] = resolvedAddress;
|
||||||
<ENSNameLink
|
const renderer = resolverRendererRegistry.get(resolver);
|
||||||
name={name}
|
if (renderer === undefined) {
|
||||||
address={address}
|
return (
|
||||||
dontOverrideColors={dontOverrideColors}
|
<PlainAddress
|
||||||
/>
|
address={address}
|
||||||
) : (
|
linkable={linkable}
|
||||||
<AddressLink
|
dontOverrideColors={dontOverrideColors}
|
||||||
address={address}
|
/>
|
||||||
text={text}
|
);
|
||||||
dontOverrideColors={dontOverrideColors}
|
}
|
||||||
/>
|
|
||||||
)}
|
return renderer(address, resolvedName, linkable, !!dontOverrideColors);
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddressOrENSName;
|
export default AddressOrENSName;
|
||||||
|
|
|
@ -5,36 +5,31 @@ import { faBomb } from "@fortawesome/free-solid-svg-icons/faBomb";
|
||||||
import { faMoneyBillAlt } from "@fortawesome/free-solid-svg-icons/faMoneyBillAlt";
|
import { faMoneyBillAlt } from "@fortawesome/free-solid-svg-icons/faMoneyBillAlt";
|
||||||
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
|
||||||
import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
|
import { faCoins } from "@fortawesome/free-solid-svg-icons/faCoins";
|
||||||
import TokenLogo from "./TokenLogo";
|
|
||||||
import AddressOrENSName from "./AddressOrENSName";
|
import AddressOrENSName from "./AddressOrENSName";
|
||||||
import { AddressContext, TokenMeta, ZERO_ADDRESS } from "../types";
|
import { AddressContext, ZERO_ADDRESS } from "../types";
|
||||||
import { ResolvedAddresses } from "../api/address-resolver";
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type DecoratedAddressLinkProps = {
|
type DecoratedAddressLinkProps = {
|
||||||
address: string;
|
address: string;
|
||||||
selectedAddress?: string;
|
selectedAddress?: string;
|
||||||
text?: string;
|
|
||||||
addressCtx?: AddressContext;
|
addressCtx?: AddressContext;
|
||||||
creation?: boolean;
|
creation?: boolean;
|
||||||
miner?: boolean;
|
miner?: boolean;
|
||||||
selfDestruct?: boolean;
|
selfDestruct?: boolean;
|
||||||
txFrom?: boolean;
|
txFrom?: boolean;
|
||||||
txTo?: boolean;
|
txTo?: boolean;
|
||||||
tokenMeta?: TokenMeta;
|
|
||||||
resolvedAddresses?: ResolvedAddresses | undefined;
|
resolvedAddresses?: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
|
const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
|
||||||
address,
|
address,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
text,
|
|
||||||
addressCtx,
|
addressCtx,
|
||||||
creation,
|
creation,
|
||||||
miner,
|
miner,
|
||||||
selfDestruct,
|
selfDestruct,
|
||||||
txFrom,
|
txFrom,
|
||||||
txTo,
|
txTo,
|
||||||
tokenMeta,
|
|
||||||
resolvedAddresses,
|
resolvedAddresses,
|
||||||
}) => {
|
}) => {
|
||||||
const mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS;
|
const mint = addressCtx === AddressContext.FROM && address === ZERO_ADDRESS;
|
||||||
|
@ -75,15 +70,9 @@ const DecoratedAddressLink: React.FC<DecoratedAddressLinkProps> = ({
|
||||||
<FontAwesomeIcon icon={faCoins} size="1x" />
|
<FontAwesomeIcon icon={faCoins} size="1x" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tokenMeta && (
|
|
||||||
<div className="self-center">
|
|
||||||
<TokenLogo address={address} name={tokenMeta.name} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<AddressOrENSName
|
<AddressOrENSName
|
||||||
address={address}
|
address={address}
|
||||||
selectedAddress={selectedAddress}
|
selectedAddress={selectedAddress}
|
||||||
text={text}
|
|
||||||
dontOverrideColors={mint || burn}
|
dontOverrideColors={mint || burn}
|
||||||
resolvedAddresses={resolvedAddresses}
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,25 +1,75 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver";
|
||||||
import ENSLogo from "./ensLogo.svg";
|
import ENSLogo from "./ensLogo.svg";
|
||||||
|
|
||||||
type ENSNameProps = {
|
type ENSNameProps = {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
linkable: boolean;
|
||||||
|
dontOverrideColors?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ENSName: React.FC<ENSNameProps> = ({ name, address }) => (
|
const ENSName: React.FC<ENSNameProps> = ({
|
||||||
<div
|
name,
|
||||||
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
|
address,
|
||||||
title={`${name}: ${address}`}
|
linkable,
|
||||||
>
|
dontOverrideColors,
|
||||||
|
}) => {
|
||||||
|
if (linkable) {
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
className={`flex items-baseline space-x-1 font-sans ${
|
||||||
|
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
||||||
|
} truncate`}
|
||||||
|
to={`/address/${name}`}
|
||||||
|
title={`${name}: ${address}`}
|
||||||
|
>
|
||||||
|
<Content linkable={true} name={name} />
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
|
||||||
|
title={`${name}: ${address}`}
|
||||||
|
>
|
||||||
|
<Content linkable={false} name={name} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContentProps = {
|
||||||
|
linkable: boolean;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Content: React.FC<ContentProps> = ({ linkable, name }) => (
|
||||||
|
<>
|
||||||
<img
|
<img
|
||||||
className="self-center filter grayscale"
|
className={`self-center ${linkable ? "" : "filter grayscale"}`}
|
||||||
src={ENSLogo}
|
src={ENSLogo}
|
||||||
alt="ENS Logo"
|
alt="ENS Logo"
|
||||||
width={12}
|
width={12}
|
||||||
height={12}
|
height={12}
|
||||||
/>
|
/>
|
||||||
<span className="truncate">{name}</span>
|
<span className="truncate">{name}</span>
|
||||||
</div>
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ensRenderer: ResolvedAddressRenderer<string> = (
|
||||||
|
address,
|
||||||
|
resolvedAddress,
|
||||||
|
linkable,
|
||||||
|
dontOverrideColors
|
||||||
|
) => (
|
||||||
|
<ENSName
|
||||||
|
address={address}
|
||||||
|
name={resolvedAddress}
|
||||||
|
linkable={linkable}
|
||||||
|
dontOverrideColors={dontOverrideColors}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ENSName;
|
export default ENSName;
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { NavLink } from "react-router-dom";
|
|
||||||
import ENSLogo from "./ensLogo.svg";
|
|
||||||
|
|
||||||
type ENSNameLinkProps = {
|
|
||||||
name: string;
|
|
||||||
address: string;
|
|
||||||
dontOverrideColors?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ENSNameLink: React.FC<ENSNameLinkProps> = ({
|
|
||||||
name,
|
|
||||||
address,
|
|
||||||
dontOverrideColors,
|
|
||||||
}) => (
|
|
||||||
<NavLink
|
|
||||||
className={`flex items-baseline space-x-1 font-sans ${
|
|
||||||
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
|
||||||
} truncate`}
|
|
||||||
to={`/address/${name}`}
|
|
||||||
title={`${name}: ${address}`}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="self-center"
|
|
||||||
src={ENSLogo}
|
|
||||||
alt="ENS Logo"
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
/>
|
|
||||||
<span className="truncate">{name}</span>
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ENSNameLink;
|
|
|
@ -3,17 +3,23 @@ import InternalTransfer from "./InternalTransfer";
|
||||||
import InternalSelfDestruct from "./InternalSelfDestruct";
|
import InternalSelfDestruct from "./InternalSelfDestruct";
|
||||||
import InternalCreate from "./InternalCreate";
|
import InternalCreate from "./InternalCreate";
|
||||||
import { TransactionData, InternalOperation, OperationType } from "../types";
|
import { TransactionData, InternalOperation, OperationType } from "../types";
|
||||||
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type InternalTransactionOperationProps = {
|
type InternalTransactionOperationProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
internalOp: InternalOperation;
|
internalOp: InternalOperation;
|
||||||
|
resolvedAddresses: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InternalTransactionOperation: React.FC<InternalTransactionOperationProps> =
|
const InternalTransactionOperation: React.FC<InternalTransactionOperationProps> =
|
||||||
({ txData, internalOp }) => (
|
({ txData, internalOp, resolvedAddresses }) => (
|
||||||
<>
|
<>
|
||||||
{internalOp.type === OperationType.TRANSFER && (
|
{internalOp.type === OperationType.TRANSFER && (
|
||||||
<InternalTransfer txData={txData} internalOp={internalOp} />
|
<InternalTransfer
|
||||||
|
txData={txData}
|
||||||
|
internalOp={internalOp}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{internalOp.type === OperationType.SELF_DESTRUCT && (
|
{internalOp.type === OperationType.SELF_DESTRUCT && (
|
||||||
<InternalSelfDestruct txData={txData} internalOp={internalOp} />
|
<InternalSelfDestruct txData={txData} internalOp={internalOp} />
|
||||||
|
|
|
@ -5,15 +5,18 @@ import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight";
|
||||||
import AddressHighlighter from "./AddressHighlighter";
|
import AddressHighlighter from "./AddressHighlighter";
|
||||||
import DecoratedAddressLink from "./DecoratedAddressLink";
|
import DecoratedAddressLink from "./DecoratedAddressLink";
|
||||||
import { TransactionData, InternalOperation } from "../types";
|
import { TransactionData, InternalOperation } from "../types";
|
||||||
|
import { ResolvedAddresses } from "../api/address-resolver";
|
||||||
|
|
||||||
type InternalTransferProps = {
|
type InternalTransferProps = {
|
||||||
txData: TransactionData;
|
txData: TransactionData;
|
||||||
internalOp: InternalOperation;
|
internalOp: InternalOperation;
|
||||||
|
resolvedAddresses: ResolvedAddresses | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InternalTransfer: React.FC<InternalTransferProps> = ({
|
const InternalTransfer: React.FC<InternalTransferProps> = ({
|
||||||
txData,
|
txData,
|
||||||
internalOp,
|
internalOp,
|
||||||
|
resolvedAddresses,
|
||||||
}) => {
|
}) => {
|
||||||
const fromMiner =
|
const fromMiner =
|
||||||
txData.confirmedData?.miner !== undefined &&
|
txData.confirmedData?.miner !== undefined &&
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from "react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
|
type PlainAddressProps = {
|
||||||
|
address: string;
|
||||||
|
linkable: boolean;
|
||||||
|
dontOverrideColors: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PlainAddress: React.FC<PlainAddressProps> = ({
|
||||||
|
address,
|
||||||
|
linkable,
|
||||||
|
dontOverrideColors,
|
||||||
|
}) => {
|
||||||
|
if (linkable) {
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
className={`${
|
||||||
|
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
||||||
|
} font-address truncate`}
|
||||||
|
to={`/address/${address}`}
|
||||||
|
title={address}
|
||||||
|
>
|
||||||
|
{address}
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="font-address text-gray-400 truncate" title={address}>
|
||||||
|
{address}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlainAddress;
|
|
@ -9,7 +9,7 @@ type TokenLogoProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const TokenLogo: React.FC<TokenLogoProps> = (props) => (
|
const TokenLogo: React.FC<TokenLogoProps> = (props) => (
|
||||||
<Suspense fallback={<></>}>
|
<Suspense fallback={null}>
|
||||||
<InternalTokenLogo {...props} />
|
<InternalTokenLogo {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React from "react";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import TokenLogo from "./TokenLogo";
|
||||||
|
import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver";
|
||||||
|
import { TokenMeta } from "../types";
|
||||||
|
|
||||||
|
type TokenNameProps = {
|
||||||
|
address: string;
|
||||||
|
name: string;
|
||||||
|
symbol: string;
|
||||||
|
linkable: boolean;
|
||||||
|
dontOverrideColors?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenName: React.FC<TokenNameProps> = ({
|
||||||
|
address,
|
||||||
|
name,
|
||||||
|
symbol,
|
||||||
|
linkable,
|
||||||
|
dontOverrideColors,
|
||||||
|
}) => {
|
||||||
|
if (linkable) {
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
className={`flex items-baseline space-x-1 font-sans ${
|
||||||
|
dontOverrideColors ? "" : "text-link-blue hover:text-link-blue-hover"
|
||||||
|
} truncate`}
|
||||||
|
to={`/address/${address}`}
|
||||||
|
title={`${name} (${symbol}): ${address}`}
|
||||||
|
>
|
||||||
|
<Content
|
||||||
|
address={address}
|
||||||
|
linkable={true}
|
||||||
|
name={name}
|
||||||
|
symbol={symbol}
|
||||||
|
/>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
|
||||||
|
title={`${name} (${symbol}): ${address}`}
|
||||||
|
>
|
||||||
|
<Content address={address} linkable={false} name={name} symbol={symbol} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContentProps = {
|
||||||
|
address: string;
|
||||||
|
name: string;
|
||||||
|
symbol: string;
|
||||||
|
linkable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Content: React.FC<ContentProps> = ({
|
||||||
|
address,
|
||||||
|
name,
|
||||||
|
symbol,
|
||||||
|
linkable,
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`self-center w-5 h-5 ${linkable ? "" : "filter grayscale"}`}
|
||||||
|
>
|
||||||
|
<TokenLogo address={address} name={name} />
|
||||||
|
</div>
|
||||||
|
<span className="truncate">
|
||||||
|
{name} ({symbol})
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const tokenRenderer: ResolvedAddressRenderer<TokenMeta> = (
|
||||||
|
address,
|
||||||
|
tokenMeta,
|
||||||
|
linkable,
|
||||||
|
dontOverrideColors
|
||||||
|
) => (
|
||||||
|
<TokenName
|
||||||
|
address={address}
|
||||||
|
name={tokenMeta.name}
|
||||||
|
symbol={tokenMeta.symbol}
|
||||||
|
linkable={linkable}
|
||||||
|
dontOverrideColors={dontOverrideColors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TokenName;
|
|
@ -206,6 +206,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
key={i}
|
key={i}
|
||||||
txData={txData}
|
txData={txData}
|
||||||
internalOp={op}
|
internalOp={op}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,6 +226,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||||
t={t}
|
t={t}
|
||||||
txData={txData}
|
txData={txData}
|
||||||
tokenMeta={txData.tokenMetas[t.token]}
|
tokenMeta={txData.tokenMetas[t.token]}
|
||||||
|
resolvedAddresses={resolvedAddresses}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { JsonRpcProvider } from "@ethersproject/providers";
|
import { JsonRpcProvider } from "@ethersproject/providers";
|
||||||
import { ProcessedTransaction, TransactionData } from "./types";
|
import { ProcessedTransaction, TransactionData } from "./types";
|
||||||
import { batchPopulate, ResolvedAddresses } from "./api/address-resolver";
|
import { batchPopulate, ResolvedAddresses } from "./api/address-resolver";
|
||||||
|
@ -66,19 +66,27 @@ export const useResolvedAddresses = (
|
||||||
addrCollector: AddressCollector
|
addrCollector: AddressCollector
|
||||||
) => {
|
) => {
|
||||||
const [names, setNames] = useState<ResolvedAddresses>();
|
const [names, setNames] = useState<ResolvedAddresses>();
|
||||||
|
const ref = useRef<ResolvedAddresses | undefined>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!provider) {
|
ref.current = names;
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const populate = async () => {
|
useEffect(
|
||||||
const _addresses = addrCollector();
|
() => {
|
||||||
const _names = await batchPopulate(provider, _addresses);
|
if (!provider) {
|
||||||
setNames(_names);
|
return;
|
||||||
};
|
}
|
||||||
populate();
|
|
||||||
}, [provider, addrCollector]);
|
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;
|
return names;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue