Add ERC token address resolver

This commit is contained in:
Willian Mitsuda 2021-10-31 19:51:11 -03:00
parent 59ff4fd401
commit 099c1465b0
7 changed files with 102 additions and 18 deletions

View File

@ -3,7 +3,7 @@ import { IAddressResolver } from "./address-resolver";
export type SelectedResolvedName<T> = [IAddressResolver<T>, T];
export class CompositeAddressResolver<T>
export class CompositeAddressResolver<T = any>
implements IAddressResolver<SelectedResolvedName<T>>
{
private resolvers: IAddressResolver<T>[] = [];
@ -17,9 +17,9 @@ export class CompositeAddressResolver<T>
address: string
): Promise<SelectedResolvedName<T> | undefined> {
for (const r of this.resolvers) {
const name = await r.resolveAddress(provider, address);
if (name !== undefined) {
return [r, name];
const resolvedAddress = await r.resolveAddress(provider, address);
if (resolvedAddress !== undefined) {
return [r, resolvedAddress];
}
}

View File

@ -2,12 +2,13 @@ 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<string> {
export class ERCTokenResolver implements IAddressResolver<TokenMeta> {
async resolveAddress(
provider: BaseProvider,
address: string
): Promise<string | undefined> {
): Promise<TokenMeta | undefined> {
const erc20Contract = new Contract(address, erc20, provider);
try {
const [name, symbol, decimals] = await Promise.all([
@ -15,9 +16,14 @@ export class ERCTokenResolver implements IAddressResolver<string> {
erc20Contract.symbol(),
erc20Contract.decimals(),
]);
return name;
return {
name,
symbol,
decimals,
};
} 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;
}

View File

@ -8,9 +8,9 @@ export interface IAddressResolver<T> {
): Promise<T | undefined>;
}
export type ResolvedAddressRenderer = (
export type ResolvedAddressRenderer<T> = (
address: string,
resolvedAddress: string,
resolvedAddress: T,
linkable: boolean,
dontOverrideColors: boolean
) => React.ReactElement;

View File

@ -1,34 +1,39 @@
import { BaseProvider } from "@ethersproject/providers";
import { ensRenderer } from "../../components/ENSName";
import { tokenRenderer } from "../../components/TokenName";
import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver";
import {
CompositeAddressResolver,
SelectedResolvedName,
} from "./CompositeAddressResolver";
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
export const ensResolver = new ENSAddressResolver();
export const ercTokenResolver = new ERCTokenResolver();
const _mainResolver = new CompositeAddressResolver<string>();
const _mainResolver = new CompositeAddressResolver();
_mainResolver.addResolver(ensResolver);
_mainResolver.addResolver(ercTokenResolver);
export const mainResolver: IAddressResolver<SelectedResolvedName<string>> =
export const mainResolver: IAddressResolver<SelectedResolvedName<any>> =
_mainResolver;
export const resolverRendererRegistry = new Map<
IAddressResolver<string>,
ResolvedAddressRenderer
IAddressResolver<any>,
ResolvedAddressRenderer<any>
>();
resolverRendererRegistry.set(ensResolver, ensRenderer);
resolverRendererRegistry.set(ercTokenResolver, tokenRenderer);
export const batchPopulate = async (
provider: BaseProvider,
addresses: string[]
): Promise<ResolvedAddresses> => {
const solvers: Promise<SelectedResolvedName<string> | undefined>[] = [];
const solvers: Promise<SelectedResolvedName<any> | undefined>[] = [];
for (const a of addresses) {
solvers.push(mainResolver.resolveAddress(provider, a));
}

View File

@ -58,7 +58,7 @@ const Content: React.FC<ContentProps> = ({ linkable, name }) => (
</>
);
export const ensRenderer: ResolvedAddressRenderer = (
export const ensRenderer: ResolvedAddressRenderer<string> = (
address,
resolvedAddress,
linkable,

View File

@ -9,7 +9,7 @@ type TokenLogoProps = {
};
const TokenLogo: React.FC<TokenLogoProps> = (props) => (
<Suspense fallback={<></>}>
<Suspense fallback={null}>
<InternalTokenLogo {...props} />
</Suspense>
);

View File

@ -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;