Add uniswap V3 resolver

This commit is contained in:
Willian Mitsuda 2021-12-03 16:14:09 -03:00
parent e6ec78daee
commit 14186ad151
3 changed files with 216 additions and 2 deletions

View File

@ -0,0 +1,90 @@
import { BaseProvider } from "@ethersproject/providers";
import { Contract } from "@ethersproject/contracts";
import { IAddressResolver } from "./address-resolver";
import { ChecksummedAddress, TokenMeta } from "../../types";
import { ERCTokenResolver } from "./ERCTokenResolver";
const UNISWAP_V3_FACTORY = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
const UNISWAP_V3_FACTORY_ABI = [
"function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)",
];
const UNISWAP_V3_PAIR_ABI = [
"function factory() external view returns (address)",
"function token0() external view returns (address)",
"function token1() external view returns (address)",
"function fee() external view returns (uint24)",
];
export type UniswapV3TokenMeta = {
address: ChecksummedAddress;
} & TokenMeta;
export type UniswapV3PairMeta = {
pair: ChecksummedAddress;
token0: UniswapV3TokenMeta;
token1: UniswapV3TokenMeta;
fee: number;
};
const ercResolver = new ERCTokenResolver();
export class UniswapV3Resolver implements IAddressResolver<UniswapV3PairMeta> {
async resolveAddress(
provider: BaseProvider,
address: string
): Promise<UniswapV3PairMeta | undefined> {
const poolContract = new Contract(address, UNISWAP_V3_PAIR_ABI, provider);
const factoryContract = new Contract(
UNISWAP_V3_FACTORY,
UNISWAP_V3_FACTORY_ABI,
provider
);
try {
// First, probe the factory() function; if it responds with UniswapV2 factory
// address, it may be a pair
const factoryAddress = (await poolContract.factory()) as string;
if (factoryAddress !== UNISWAP_V3_FACTORY) {
return undefined;
}
// Probe the token0/token1/fee
const [token0, token1, fee] = await Promise.all([
poolContract.token0() as string,
poolContract.token1() as string,
poolContract.fee() as number,
]);
// Probe the factory to ensure it is a legit pair
const expectedPoolAddress = await factoryContract.getPool(
token0,
token1,
fee
);
if (expectedPoolAddress !== address) {
return undefined;
}
const [meta0, meta1] = await Promise.all([
ercResolver.resolveAddress(provider, token0),
ercResolver.resolveAddress(provider, token1),
]);
if (meta0 === undefined || meta1 === undefined) {
return undefined;
}
return {
pair: address,
token0: { address: token0, ...meta0 },
token1: { address: token1, ...meta1 },
fee,
};
} catch (err) {
// Ignore on purpose; this indicates the probe failed and the address
// is not a token
}
return undefined;
}
}

View File

@ -4,6 +4,7 @@ import { plainStringRenderer } from "../../components/PlainString";
import { tokenRenderer } from "../../components/TokenName";
import { uniswapV1PairRenderer } from "../../components/UniswapV1ExchangeName";
import { uniswapV2PairRenderer } from "../../components/UniswapV2PairName";
import { uniswapV3PairRenderer } from "../../components/UniswapV3PoolName";
import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver";
import {
CompositeAddressResolver,
@ -12,6 +13,7 @@ import {
import { ENSAddressResolver } from "./ENSAddressResolver";
import { UniswapV1Resolver } from "./UniswapV1Resolver";
import { UniswapV2Resolver } from "./UniswapV2Resolver";
import { UniswapV3Resolver } from "./UniswapV3Resolver";
import { ERCTokenResolver } from "./ERCTokenResolver";
import { HardcodedAddressResolver } from "./HardcodedAddressResolver";
@ -19,13 +21,15 @@ export type ResolvedAddresses = Record<string, SelectedResolvedName<any>>;
// Create and configure the main resolver
export const ensResolver = new ENSAddressResolver();
export const uniswapV2Resolver = new UniswapV2Resolver();
export const uniswapV1Resolver = new UniswapV1Resolver();
export const uniswapV2Resolver = new UniswapV2Resolver();
export const uniswapV3Resolver = new UniswapV3Resolver();
export const ercTokenResolver = new ERCTokenResolver();
export const hardcodedResolver = new HardcodedAddressResolver();
const _mainResolver = new CompositeAddressResolver();
_mainResolver.addResolver(ensResolver);
_mainResolver.addResolver(uniswapV3Resolver);
_mainResolver.addResolver(uniswapV2Resolver);
_mainResolver.addResolver(uniswapV1Resolver);
_mainResolver.addResolver(ercTokenResolver);
@ -39,8 +43,9 @@ export const resolverRendererRegistry = new Map<
ResolvedAddressRenderer<any>
>();
resolverRendererRegistry.set(ensResolver, ensRenderer);
resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer);
resolverRendererRegistry.set(uniswapV1Resolver, uniswapV1PairRenderer);
resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer);
resolverRendererRegistry.set(uniswapV3Resolver, uniswapV3PairRenderer);
resolverRendererRegistry.set(ercTokenResolver, tokenRenderer);
resolverRendererRegistry.set(hardcodedResolver, plainStringRenderer);

View File

@ -0,0 +1,119 @@
import React from "react";
import { NavLink } from "react-router-dom";
import TokenLogo from "./TokenLogo";
import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver";
import {
UniswapV3PairMeta,
UniswapV3TokenMeta,
} from "../api/address-resolver/UniswapV3Resolver";
import { ChecksummedAddress } from "../types";
type UniswapV3PoolNameProps = {
address: string;
token0: UniswapV3TokenMeta;
token1: UniswapV3TokenMeta;
fee: number;
linkable: boolean;
dontOverrideColors?: boolean;
};
const UniswapV3PairName: React.FC<UniswapV3PoolNameProps> = ({
address,
token0,
token1,
fee,
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={`Uniswap V3 LP (${token0.symbol}/${token1.symbol}/${
fee / 10000
}%): ${address}`}
>
<span>Uniswap V3 LP:</span>
<Content
linkable={true}
address={token0.address}
name={token0.name}
symbol={token0.symbol}
/>
<span>/</span>
<Content
linkable={true}
address={token1.address}
name={token1.name}
symbol={token1.symbol}
/>
<span>/ {fee / 10000}%</span>
</NavLink>
);
}
return (
<div
className="flex items-baseline space-x-1 font-sans text-gray-700 truncate"
title={`Uniswap V3 LP (${token0.symbol}/${token1.symbol}/${
fee / 10000
}%): ${address}`}
>
<span>Uniswap V3 LP:</span>
<Content
linkable={false}
address={token0.address}
name={token0.name}
symbol={token0.symbol}
/>
<span>/</span>
<Content
linkable={false}
address={token1.address}
name={token1.name}
symbol={token1.symbol}
/>
<span>/ {fee / 10000}%</span>
</div>
);
};
type ContentProps = {
linkable: boolean;
address: ChecksummedAddress;
name: string;
symbol: string;
};
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>{symbol}</span>
</>
);
export const uniswapV3PairRenderer: ResolvedAddressRenderer<UniswapV3PairMeta> =
(address, tokenMeta, linkable, dontOverrideColors) => (
<UniswapV3PairName
address={address}
token0={tokenMeta.token0}
token1={tokenMeta.token1}
fee={tokenMeta.fee}
linkable={linkable}
dontOverrideColors={dontOverrideColors}
/>
);
export default UniswapV3PairName;