From c58d9afb38a713bb5b7d8ca2d56f232811e5e990 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 3 Dec 2021 08:37:09 -0300 Subject: [PATCH 1/5] First working iteration of uniswap V2 LP address resolver --- src/TokenTransferItem.tsx | 6 +- src/api/address-resolver/UniswapV2Resolver.ts | 83 +++++++++++++ src/api/address-resolver/index.ts | 5 + src/components/UniswapV2PairName.tsx | 110 ++++++++++++++++++ 4 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/api/address-resolver/UniswapV2Resolver.ts create mode 100644 src/components/UniswapV2PairName.tsx diff --git a/src/TokenTransferItem.tsx b/src/TokenTransferItem.tsx index 59fffbb..9e49ee4 100644 --- a/src/TokenTransferItem.tsx +++ b/src/TokenTransferItem.tsx @@ -31,8 +31,8 @@ const TokenTransferItem: React.FC = ({ -
-
+
+
From = ({ metadata={metadatas[t.from]} />
-
+
To { + async resolveAddress( + provider: BaseProvider, + address: string + ): Promise { + const pairContract = new Contract(address, UNISWAP_V2_PAIR_ABI, provider); + const factoryContract = new Contract( + UNISWAP_V2_FACTORY, + UNISWAP_V2_FACTORY_ABI, + provider + ); + + try { + // First, probe the factory() function; if it responds with UniswapV2 factory + // address, it may be a pair + const factoryAddress = (await pairContract.factory()) as string; + if (factoryAddress !== UNISWAP_V2_FACTORY) { + return undefined; + } + + // Probe the token0/token1 + const [token0, token1] = await Promise.all([ + pairContract.token0() as string, + pairContract.token1() as string, + ]); + + // Probe the factory to ensure it is a legit pair + const expectedPairAddress = await factoryContract.getPair(token0, token1); + if (expectedPairAddress !== address) { + return undefined; + } + + console.log(`Found pair: ${token0}/${token1}`); + 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 }, + }; + } catch (err) { + // Ignore on purpose; this indicates the probe failed and the address + // is not a token + } + return undefined; + } +} diff --git a/src/api/address-resolver/index.ts b/src/api/address-resolver/index.ts index f1514be..ba96106 100644 --- a/src/api/address-resolver/index.ts +++ b/src/api/address-resolver/index.ts @@ -2,12 +2,14 @@ import { BaseProvider } from "@ethersproject/providers"; import { ensRenderer } from "../../components/ENSName"; import { plainStringRenderer } from "../../components/PlainString"; import { tokenRenderer } from "../../components/TokenName"; +import { uniswapV2PairRenderer } from "../../components/UniswapV2PairName"; import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver"; import { CompositeAddressResolver, SelectedResolvedName, } from "./CompositeAddressResolver"; import { ENSAddressResolver } from "./ENSAddressResolver"; +import { UniswapV2Resolver } from "./UniswapV2Resolver"; import { ERCTokenResolver } from "./ERCTokenResolver"; import { HardcodedAddressResolver } from "./HardcodedAddressResolver"; @@ -15,11 +17,13 @@ export type ResolvedAddresses = Record>; // Create and configure the main resolver export const ensResolver = new ENSAddressResolver(); +export const uniswapV2Resolver = new UniswapV2Resolver(); export const ercTokenResolver = new ERCTokenResolver(); export const hardcodedResolver = new HardcodedAddressResolver(); const _mainResolver = new CompositeAddressResolver(); _mainResolver.addResolver(ensResolver); +_mainResolver.addResolver(uniswapV2Resolver); _mainResolver.addResolver(ercTokenResolver); _mainResolver.addResolver(hardcodedResolver); @@ -31,6 +35,7 @@ export const resolverRendererRegistry = new Map< ResolvedAddressRenderer >(); resolverRendererRegistry.set(ensResolver, ensRenderer); +resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer); resolverRendererRegistry.set(ercTokenResolver, tokenRenderer); resolverRendererRegistry.set(hardcodedResolver, plainStringRenderer); diff --git a/src/components/UniswapV2PairName.tsx b/src/components/UniswapV2PairName.tsx new file mode 100644 index 0000000..f4520b7 --- /dev/null +++ b/src/components/UniswapV2PairName.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import TokenLogo from "./TokenLogo"; +import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver"; +import { + UniswapV2PairMeta, + UniswapV2TokenMeta, +} from "../api/address-resolver/UniswapV2Resolver"; +import { ChecksummedAddress } from "../types"; + +type UniswapV2PairNameProps = { + address: string; + token0: UniswapV2TokenMeta; + token1: UniswapV2TokenMeta; + linkable: boolean; + dontOverrideColors?: boolean; +}; + +const UniswapV2PairName: React.FC = ({ + address, + token0, + token1, + linkable, + dontOverrideColors, +}) => { + if (linkable) { + return ( + + Uniswap V2 LP: + + / + + + ); + } + + return ( +
+ Uniswap V2 LP: + + / + +
+ ); +}; + +type ContentProps = { + linkable: boolean; + address: ChecksummedAddress; + name: string; + symbol: string; +}; + +const Content: React.FC = ({ + address, + name, + symbol, + linkable, +}) => ( + <> +
+ +
+ {symbol} + +); + +export const uniswapV2PairRenderer: ResolvedAddressRenderer = + (address, tokenMeta, linkable, dontOverrideColors) => ( + + ); + +export default UniswapV2PairName; From 0233879011e4a4be0454a9e8ae6a4c2d12fdf9ca Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 3 Dec 2021 10:04:15 -0300 Subject: [PATCH 2/5] Remove console.log --- src/api/address-resolver/UniswapV2Resolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/address-resolver/UniswapV2Resolver.ts b/src/api/address-resolver/UniswapV2Resolver.ts index 7c594bc..761237c 100644 --- a/src/api/address-resolver/UniswapV2Resolver.ts +++ b/src/api/address-resolver/UniswapV2Resolver.ts @@ -60,7 +60,6 @@ export class UniswapV2Resolver implements IAddressResolver { return undefined; } - console.log(`Found pair: ${token0}/${token1}`); const [meta0, meta1] = await Promise.all([ ercResolver.resolveAddress(provider, token0), ercResolver.resolveAddress(provider, token1), From 6a15285a48844c3603caafff5d17c5f0d0f2ae00 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 3 Dec 2021 10:04:42 -0300 Subject: [PATCH 3/5] Add uniswap V1 support --- src/api/address-resolver/UniswapV1Resolver.ts | 60 ++++++++++++ src/api/address-resolver/index.ts | 5 + src/components/UniswapV1ExchangeName.tsx | 93 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/api/address-resolver/UniswapV1Resolver.ts create mode 100644 src/components/UniswapV1ExchangeName.tsx diff --git a/src/api/address-resolver/UniswapV1Resolver.ts b/src/api/address-resolver/UniswapV1Resolver.ts new file mode 100644 index 0000000..a8cb2e0 --- /dev/null +++ b/src/api/address-resolver/UniswapV1Resolver.ts @@ -0,0 +1,60 @@ +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_V1_FACTORY = "0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95"; + +const UNISWAP_V1_FACTORY_ABI = [ + "function getToken(address exchange) external view returns (address token)", +]; + +const NULL_ADDRESS = "0x0000000000000000000000000000000000000000"; + +export type UniswapV1TokenMeta = { + address: ChecksummedAddress; +} & TokenMeta; + +export type UniswapV1PairMeta = { + exchange: ChecksummedAddress; + token: UniswapV1TokenMeta; +}; + +const ercResolver = new ERCTokenResolver(); + +export class UniswapV1Resolver implements IAddressResolver { + async resolveAddress( + provider: BaseProvider, + address: string + ): Promise { + const factoryContract = new Contract( + UNISWAP_V1_FACTORY, + UNISWAP_V1_FACTORY_ABI, + provider + ); + + try { + // First, probe the getToken() function; if it responds with an UniswapV1 exchange + // address, it is a LP + const token = (await factoryContract.getToken(address)) as string; + if (token === NULL_ADDRESS) { + return undefined; + } + + const metadata = await ercResolver.resolveAddress(provider, token); + if (metadata === undefined) { + return undefined; + } + + return { + exchange: address, + token: { address: token, ...metadata }, + }; + } catch (err) { + // Ignore on purpose; this indicates the probe failed and the address + // is not a token + } + return undefined; + } +} diff --git a/src/api/address-resolver/index.ts b/src/api/address-resolver/index.ts index ba96106..5899231 100644 --- a/src/api/address-resolver/index.ts +++ b/src/api/address-resolver/index.ts @@ -2,6 +2,7 @@ import { BaseProvider } from "@ethersproject/providers"; import { ensRenderer } from "../../components/ENSName"; import { plainStringRenderer } from "../../components/PlainString"; import { tokenRenderer } from "../../components/TokenName"; +import { uniswapV1PairRenderer } from "../../components/UniswapV1ExchangeName"; import { uniswapV2PairRenderer } from "../../components/UniswapV2PairName"; import { IAddressResolver, ResolvedAddressRenderer } from "./address-resolver"; import { @@ -9,6 +10,7 @@ import { SelectedResolvedName, } from "./CompositeAddressResolver"; import { ENSAddressResolver } from "./ENSAddressResolver"; +import { UniswapV1Resolver } from "./UniswapV1Resolver"; import { UniswapV2Resolver } from "./UniswapV2Resolver"; import { ERCTokenResolver } from "./ERCTokenResolver"; import { HardcodedAddressResolver } from "./HardcodedAddressResolver"; @@ -18,12 +20,14 @@ export type ResolvedAddresses = Record>; // Create and configure the main resolver export const ensResolver = new ENSAddressResolver(); export const uniswapV2Resolver = new UniswapV2Resolver(); +export const uniswapV1Resolver = new UniswapV1Resolver(); export const ercTokenResolver = new ERCTokenResolver(); export const hardcodedResolver = new HardcodedAddressResolver(); const _mainResolver = new CompositeAddressResolver(); _mainResolver.addResolver(ensResolver); _mainResolver.addResolver(uniswapV2Resolver); +_mainResolver.addResolver(uniswapV1Resolver); _mainResolver.addResolver(ercTokenResolver); _mainResolver.addResolver(hardcodedResolver); @@ -36,6 +40,7 @@ export const resolverRendererRegistry = new Map< >(); resolverRendererRegistry.set(ensResolver, ensRenderer); resolverRendererRegistry.set(uniswapV2Resolver, uniswapV2PairRenderer); +resolverRendererRegistry.set(uniswapV1Resolver, uniswapV1PairRenderer); resolverRendererRegistry.set(ercTokenResolver, tokenRenderer); resolverRendererRegistry.set(hardcodedResolver, plainStringRenderer); diff --git a/src/components/UniswapV1ExchangeName.tsx b/src/components/UniswapV1ExchangeName.tsx new file mode 100644 index 0000000..710c543 --- /dev/null +++ b/src/components/UniswapV1ExchangeName.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import TokenLogo from "./TokenLogo"; +import { ResolvedAddressRenderer } from "../api/address-resolver/address-resolver"; +import { ChecksummedAddress } from "../types"; +import { + UniswapV1PairMeta, + UniswapV1TokenMeta, +} from "../api/address-resolver/UniswapV1Resolver"; + +type UniswapV1ExchangeNameProps = { + address: string; + token: UniswapV1TokenMeta; + linkable: boolean; + dontOverrideColors?: boolean; +}; + +const UniswapV1ExchangeName: React.FC = ({ + address, + token, + linkable, + dontOverrideColors, +}) => { + if (linkable) { + return ( + + Uniswap V1 LP: + + + ); + } + + return ( +
+ Uniswap V1 LP: + +
+ ); +}; + +type ContentProps = { + linkable: boolean; + address: ChecksummedAddress; + name: string; + symbol: string; +}; + +const Content: React.FC = ({ + address, + name, + symbol, + linkable, +}) => ( + <> +
+ +
+ {symbol} + +); + +export const uniswapV1PairRenderer: ResolvedAddressRenderer = + (address, tokenMeta, linkable, dontOverrideColors) => ( + + ); + +export default UniswapV1ExchangeName; From e6ec78daee2c7c40ed23606e5e3a7bd148949725 Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 3 Dec 2021 15:46:50 -0300 Subject: [PATCH 4/5] Add all Uniswap V3 known deployments from https://github.com/Uniswap/v3-periphery/blob/main/deploys.md --- src/api/address-resolver/hardcoded-addresses/1.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/api/address-resolver/hardcoded-addresses/1.json b/src/api/address-resolver/hardcoded-addresses/1.json index c05f60a..0164374 100644 --- a/src/api/address-resolver/hardcoded-addresses/1.json +++ b/src/api/address-resolver/hardcoded-addresses/1.json @@ -14,7 +14,17 @@ "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f": "Uniswap V2: Factory", "0xf164fC0Ec4E93095b804a4795bBe1e041497b92a": "Uniswap V2: Router 1", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D": "Uniswap V2: Router 2", - "0x1F98431c8aD98523631AE4a59f267346ea31F984": "Uniswap V3: Router", + "0x1F98431c8aD98523631AE4a59f267346ea31F984": "Uniswap V3: Factory", + "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696": "Uniswap V3: Multicall2", + "0xB753548F6E010e7e680BA186F9Ca1BdAB2E90cf2": "Uniswap V3: ProxyAdmin", + "0xbfd8137f7d1516D3ea5cA83523914859ec47F573": "Uniswap V3: TickLens", + "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6": "Uniswap V3: Quoter", + "0xE592427A0AEce92De3Edee1F18E0157C05861564": "Uniswap V3: Router", + "0x42B24A95702b9986e82d421cC3568932790A48Ec": "Uniswap V3: NFT Descriptor", + "0x91ae842A5Ffd8d12023116943e72A606179294f3": "Uniswap V3: NFT Position Descriptor", + "0xEe6A57eC80ea46401049E92587E52f5Ec1c24785": "Uniswap V3: Transparent Upgradeable Proxy", + "0xC36442b4a4522E871399CD717aBDD847Ab11FE88": "Uniswap V3: Nonfungible Position Manager", + "0xA5644E29708357803b5A882D272c41cC0dF92B34": "Uniswap V3: V3 Migrator", "0x722122dF12D4e14e13Ac3b6895a86e84145b6967": "Tornado Cash: Proxy", "0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc": "Tornado Cash: 0.1 ETH", "0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936": "Tornado Cash: 1 ETH", From 14186ad151d86a0dd929c2a4e225aa2f95e33e2a Mon Sep 17 00:00:00 2001 From: Willian Mitsuda Date: Fri, 3 Dec 2021 16:14:09 -0300 Subject: [PATCH 5/5] Add uniswap V3 resolver --- src/api/address-resolver/UniswapV3Resolver.ts | 90 +++++++++++++ src/api/address-resolver/index.ts | 9 +- src/components/UniswapV3PoolName.tsx | 119 ++++++++++++++++++ 3 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 src/api/address-resolver/UniswapV3Resolver.ts create mode 100644 src/components/UniswapV3PoolName.tsx diff --git a/src/api/address-resolver/UniswapV3Resolver.ts b/src/api/address-resolver/UniswapV3Resolver.ts new file mode 100644 index 0000000..835e823 --- /dev/null +++ b/src/api/address-resolver/UniswapV3Resolver.ts @@ -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 { + async resolveAddress( + provider: BaseProvider, + address: string + ): Promise { + 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; + } +} diff --git a/src/api/address-resolver/index.ts b/src/api/address-resolver/index.ts index 5899231..c6a1687 100644 --- a/src/api/address-resolver/index.ts +++ b/src/api/address-resolver/index.ts @@ -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>; // 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 >(); 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); diff --git a/src/components/UniswapV3PoolName.tsx b/src/components/UniswapV3PoolName.tsx new file mode 100644 index 0000000..a21b186 --- /dev/null +++ b/src/components/UniswapV3PoolName.tsx @@ -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 = ({ + address, + token0, + token1, + fee, + linkable, + dontOverrideColors, +}) => { + if (linkable) { + return ( + + Uniswap V3 LP: + + / + + / {fee / 10000}% + + ); + } + + return ( +
+ Uniswap V3 LP: + + / + + / {fee / 10000}% +
+ ); +}; + +type ContentProps = { + linkable: boolean; + address: ChecksummedAddress; + name: string; + symbol: string; +}; + +const Content: React.FC = ({ + address, + name, + symbol, + linkable, +}) => ( + <> +
+ +
+ {symbol} + +); + +export const uniswapV3PairRenderer: ResolvedAddressRenderer = + (address, tokenMeta, linkable, dontOverrideColors) => ( + + ); + +export default UniswapV3PairName;