Add ERC token address resolver
This commit is contained in:
parent
59ff4fd401
commit
099c1465b0
|
@ -3,7 +3,7 @@ import { IAddressResolver } from "./address-resolver";
|
||||||
|
|
||||||
export type SelectedResolvedName<T> = [IAddressResolver<T>, T];
|
export type SelectedResolvedName<T> = [IAddressResolver<T>, T];
|
||||||
|
|
||||||
export class CompositeAddressResolver<T>
|
export class CompositeAddressResolver<T = any>
|
||||||
implements IAddressResolver<SelectedResolvedName<T>>
|
implements IAddressResolver<SelectedResolvedName<T>>
|
||||||
{
|
{
|
||||||
private resolvers: IAddressResolver<T>[] = [];
|
private resolvers: IAddressResolver<T>[] = [];
|
||||||
|
@ -17,9 +17,9 @@ export class CompositeAddressResolver<T>
|
||||||
address: string
|
address: string
|
||||||
): Promise<SelectedResolvedName<T> | undefined> {
|
): Promise<SelectedResolvedName<T> | undefined> {
|
||||||
for (const r of this.resolvers) {
|
for (const r of this.resolvers) {
|
||||||
const name = await r.resolveAddress(provider, address);
|
const resolvedAddress = await r.resolveAddress(provider, address);
|
||||||
if (name !== undefined) {
|
if (resolvedAddress !== undefined) {
|
||||||
return [r, name];
|
return [r, resolvedAddress];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,13 @@ import { BaseProvider } from "@ethersproject/providers";
|
||||||
import { Contract } from "@ethersproject/contracts";
|
import { Contract } from "@ethersproject/contracts";
|
||||||
import { IAddressResolver } from "./address-resolver";
|
import { IAddressResolver } from "./address-resolver";
|
||||||
import erc20 from "../../erc20.json";
|
import erc20 from "../../erc20.json";
|
||||||
|
import { TokenMeta } from "../../types";
|
||||||
|
|
||||||
export class ERCTokenResolver implements IAddressResolver<string> {
|
export class ERCTokenResolver implements IAddressResolver<TokenMeta> {
|
||||||
async resolveAddress(
|
async resolveAddress(
|
||||||
provider: BaseProvider,
|
provider: BaseProvider,
|
||||||
address: string
|
address: string
|
||||||
): Promise<string | undefined> {
|
): Promise<TokenMeta | undefined> {
|
||||||
const erc20Contract = new Contract(address, erc20, provider);
|
const erc20Contract = new Contract(address, erc20, provider);
|
||||||
try {
|
try {
|
||||||
const [name, symbol, decimals] = await Promise.all([
|
const [name, symbol, decimals] = await Promise.all([
|
||||||
|
@ -15,9 +16,14 @@ export class ERCTokenResolver implements IAddressResolver<string> {
|
||||||
erc20Contract.symbol(),
|
erc20Contract.symbol(),
|
||||||
erc20Contract.decimals(),
|
erc20Contract.decimals(),
|
||||||
]);
|
]);
|
||||||
return name;
|
return {
|
||||||
|
name,
|
||||||
|
symbol,
|
||||||
|
decimals,
|
||||||
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`Couldn't get token ${address} metadata; ignoring`, err);
|
// Ignore on purpose; this indicates the probe failed and the address
|
||||||
|
// is not a token
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ export interface IAddressResolver<T> {
|
||||||
): Promise<T | undefined>;
|
): Promise<T | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResolvedAddressRenderer = (
|
export type ResolvedAddressRenderer<T> = (
|
||||||
address: string,
|
address: string,
|
||||||
resolvedAddress: string,
|
resolvedAddress: T,
|
||||||
linkable: boolean,
|
linkable: boolean,
|
||||||
dontOverrideColors: boolean
|
dontOverrideColors: boolean
|
||||||
) => React.ReactElement;
|
) => React.ReactElement;
|
||||||
|
|
|
@ -1,34 +1,39 @@
|
||||||
import { BaseProvider } from "@ethersproject/providers";
|
import { BaseProvider } from "@ethersproject/providers";
|
||||||
import { ensRenderer } from "../../components/ENSName";
|
import { ensRenderer } from "../../components/ENSName";
|
||||||
|
import { tokenRenderer } from "../../components/TokenName";
|
||||||
import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver";
|
import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver";
|
||||||
import {
|
import {
|
||||||
CompositeAddressResolver,
|
CompositeAddressResolver,
|
||||||
SelectedResolvedName,
|
SelectedResolvedName,
|
||||||
} from "./CompositeAddressResolver";
|
} from "./CompositeAddressResolver";
|
||||||
import { ENSAddressResolver } from "./ENSAddressResolver";
|
import { ENSAddressResolver } from "./ENSAddressResolver";
|
||||||
|
import { ERCTokenResolver } from "./ERCTokenResolver";
|
||||||
|
|
||||||
export type ResolvedAddresses = Record<string, SelectedResolvedName<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 ensResolver = new ENSAddressResolver();
|
||||||
|
export const ercTokenResolver = new ERCTokenResolver();
|
||||||
|
|
||||||
const _mainResolver = new CompositeAddressResolver<string>();
|
const _mainResolver = new CompositeAddressResolver();
|
||||||
_mainResolver.addResolver(ensResolver);
|
_mainResolver.addResolver(ensResolver);
|
||||||
|
_mainResolver.addResolver(ercTokenResolver);
|
||||||
|
|
||||||
export const mainResolver: IAddressResolver<SelectedResolvedName<string>> =
|
export const mainResolver: IAddressResolver<SelectedResolvedName<any>> =
|
||||||
_mainResolver;
|
_mainResolver;
|
||||||
|
|
||||||
export const resolverRendererRegistry = new Map<
|
export const resolverRendererRegistry = new Map<
|
||||||
IAddressResolver<string>,
|
IAddressResolver<any>,
|
||||||
ResolvedAddressRenderer
|
ResolvedAddressRenderer<any>
|
||||||
>();
|
>();
|
||||||
resolverRendererRegistry.set(ensResolver, ensRenderer);
|
resolverRendererRegistry.set(ensResolver, ensRenderer);
|
||||||
|
resolverRendererRegistry.set(ercTokenResolver, tokenRenderer);
|
||||||
|
|
||||||
export const batchPopulate = async (
|
export const batchPopulate = async (
|
||||||
provider: BaseProvider,
|
provider: BaseProvider,
|
||||||
addresses: string[]
|
addresses: string[]
|
||||||
): Promise<ResolvedAddresses> => {
|
): Promise<ResolvedAddresses> => {
|
||||||
const solvers: Promise<SelectedResolvedName<string> | undefined>[] = [];
|
const solvers: Promise<SelectedResolvedName<any> | undefined>[] = [];
|
||||||
for (const a of addresses) {
|
for (const a of addresses) {
|
||||||
solvers.push(mainResolver.resolveAddress(provider, a));
|
solvers.push(mainResolver.resolveAddress(provider, a));
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ const Content: React.FC<ContentProps> = ({ linkable, name }) => (
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ensRenderer: ResolvedAddressRenderer = (
|
export const ensRenderer: ResolvedAddressRenderer<string> = (
|
||||||
address,
|
address,
|
||||||
resolvedAddress,
|
resolvedAddress,
|
||||||
linkable,
|
linkable,
|
||||||
|
|
|
@ -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,73 @@
|
||||||
|
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 = {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
linkable: boolean;
|
||||||
|
dontOverrideColors?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenName: React.FC<TokenNameProps> = ({
|
||||||
|
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 address={address} linkable={true} name={name} />
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
|
||||||
|
title={`${name}: ${address}`}
|
||||||
|
>
|
||||||
|
<Content address={address} linkable={false} name={name} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ContentProps = {
|
||||||
|
address: string;
|
||||||
|
linkable: boolean;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Content: React.FC<ContentProps> = ({ address, linkable, name }) => (
|
||||||
|
<>
|
||||||
|
<div className="self-center w-5 h-5">
|
||||||
|
<TokenLogo address={address} name={name} />
|
||||||
|
</div>
|
||||||
|
<span className="truncate">{name}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const tokenRenderer: ResolvedAddressRenderer<TokenMeta> = (
|
||||||
|
address,
|
||||||
|
resolvedAddress,
|
||||||
|
linkable,
|
||||||
|
dontOverrideColors
|
||||||
|
) => (
|
||||||
|
<TokenName
|
||||||
|
address={address}
|
||||||
|
name={resolvedAddress.name}
|
||||||
|
linkable={linkable}
|
||||||
|
dontOverrideColors={dontOverrideColors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TokenName;
|
Loading…
Reference in New Issue