From 8c2bbec5decfff0f9ac20fabf73b0992dbeb5186 Mon Sep 17 00:00:00 2001 From: William Poulin Date: Fri, 22 Apr 2022 15:14:20 -0400 Subject: [PATCH] feat: Extract uniswap-v2 to Studio (#218) * feat: Extract Uniswap-V2 to Studio * fix: updated uniswap-v2 dependencies * chore: splitted past,present and final query logic from gql request --- .../helpers/the-graph/the-graph.helper.ts | 15 + .../contracts/abis/uniswap-factory.json | 97 +++ .../contracts/abis/uniswap-pair.json | 345 +++++++++ .../contracts/ethers/UniswapFactory.ts | 229 ++++++ .../contracts/ethers/UniswapPair.ts | 721 +++++++++++++++++ .../uniswap-v2/contracts/ethers/common.ts | 30 + .../factories/UniswapFactory__factory.ts | 211 +++++ .../ethers/factories/UniswapPair__factory.ts | 731 ++++++++++++++++++ .../contracts/ethers/factories/index.ts | 5 + src/apps/uniswap-v2/contracts/ethers/index.ts | 8 + src/apps/uniswap-v2/contracts/index.ts | 28 + .../ethereum/uniswap-v2.balance-fetcher.ts | 44 ++ .../ethereum/uniswap-v2.pool.token-fetcher.ts | 90 +++ ...v2.on-chain.pool-token-address-strategy.ts | 48 ++ .../helpers/uniswap-v2.pool.token-helper.ts | 200 +++++ ...2.the-graph.pool-token-address-strategy.ts | 93 +++ ...-v2.the-graph.pool-token-balance-helper.ts | 212 +++++ ...iswap-v2.the-graph.pool-volume-strategy.ts | 214 +++++ src/apps/uniswap-v2/uniswap-v2.definition.ts | 32 + src/apps/uniswap-v2/uniswap-v2.module.ts | 37 + 20 files changed, 3390 insertions(+) create mode 100644 src/apps/uniswap-v2/contracts/abis/uniswap-factory.json create mode 100644 src/apps/uniswap-v2/contracts/abis/uniswap-pair.json create mode 100644 src/apps/uniswap-v2/contracts/ethers/UniswapFactory.ts create mode 100644 src/apps/uniswap-v2/contracts/ethers/UniswapPair.ts create mode 100644 src/apps/uniswap-v2/contracts/ethers/common.ts create mode 100644 src/apps/uniswap-v2/contracts/ethers/factories/UniswapFactory__factory.ts create mode 100644 src/apps/uniswap-v2/contracts/ethers/factories/UniswapPair__factory.ts create mode 100644 src/apps/uniswap-v2/contracts/ethers/factories/index.ts create mode 100644 src/apps/uniswap-v2/contracts/ethers/index.ts create mode 100644 src/apps/uniswap-v2/contracts/index.ts create mode 100644 src/apps/uniswap-v2/ethereum/uniswap-v2.balance-fetcher.ts create mode 100644 src/apps/uniswap-v2/ethereum/uniswap-v2.pool.token-fetcher.ts create mode 100644 src/apps/uniswap-v2/helpers/uniswap-v2.on-chain.pool-token-address-strategy.ts create mode 100644 src/apps/uniswap-v2/helpers/uniswap-v2.pool.token-helper.ts create mode 100644 src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-address-strategy.ts create mode 100644 src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-balance-helper.ts create mode 100644 src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-volume-strategy.ts create mode 100644 src/apps/uniswap-v2/uniswap-v2.definition.ts create mode 100644 src/apps/uniswap-v2/uniswap-v2.module.ts diff --git a/src/app-toolkit/helpers/the-graph/the-graph.helper.ts b/src/app-toolkit/helpers/the-graph/the-graph.helper.ts index 1817cbfab..42e1c7e48 100644 --- a/src/app-toolkit/helpers/the-graph/the-graph.helper.ts +++ b/src/app-toolkit/helpers/the-graph/the-graph.helper.ts @@ -9,10 +9,25 @@ type RequestParams = { headers?: Record; }; +type RequestGraphParams = { + endpoint: string; + query: string | { present: string; past: string }; + variables?: Variables; + headers?: Record; +}; + @Injectable() export class TheGraphHelper { async request({ endpoint, query, variables, headers }: RequestParams) { const client = new GraphQLClient(endpoint, { headers }); return client.request(query, variables); } + + async requestGraph({ endpoint, query, variables = {} }: RequestGraphParams) { + const presentQuery = typeof query === 'string' ? query : query.present; + const pastQuery = typeof query === 'string' ? null : query.past; + const finalQuery = typeof variables.blockTag === 'number' && pastQuery ? pastQuery : presentQuery; + + return this.request({ endpoint, query: finalQuery, variables }); + } } diff --git a/src/apps/uniswap-v2/contracts/abis/uniswap-factory.json b/src/apps/uniswap-v2/contracts/abis/uniswap-factory.json new file mode 100644 index 000000000..792261a11 --- /dev/null +++ b/src/apps/uniswap-v2/contracts/abis/uniswap-factory.json @@ -0,0 +1,97 @@ +[ + { + "inputs": [{ "internalType": "address", "name": "_feeToSetter", "type": "address" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "token0", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "token1", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "pair", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "PairCreated", + "type": "event" + }, + { + "constant": true, + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allPairs", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "allPairsLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "tokenA", "type": "address" }, + { "internalType": "address", "name": "tokenB", "type": "address" } + ], + "name": "createPair", + "outputs": [{ "internalType": "address", "name": "pair", "type": "address" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "feeTo", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "feeToSetter", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "getPair", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "_feeTo", "type": "address" }], + "name": "setFeeTo", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "_feeToSetter", "type": "address" }], + "name": "setFeeToSetter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/apps/uniswap-v2/contracts/abis/uniswap-pair.json b/src/apps/uniswap-v2/contracts/abis/uniswap-pair.json new file mode 100644 index 000000000..809791bab --- /dev/null +++ b/src/apps/uniswap-v2/contracts/abis/uniswap-pair.json @@ -0,0 +1,345 @@ +[ + { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amount1", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount0In", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amount1In", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amount0Out", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "amount1Out", "type": "uint256" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint112", "name": "reserve0", "type": "uint112" }, + { "indexed": false, "internalType": "uint112", "name": "reserve1", "type": "uint112" } + ], + "name": "Sync", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MINIMUM_LIQUIDITY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "burn", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getReserves", + "outputs": [ + { "internalType": "uint112", "name": "_reserve0", "type": "uint112" }, + { "internalType": "uint112", "name": "_reserve1", "type": "uint112" }, + { "internalType": "uint32", "name": "_blockTimestampLast", "type": "uint32" } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "_token0", "type": "address" }, + { "internalType": "address", "name": "_token1", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kLast", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "mint", + "outputs": [{ "internalType": "uint256", "name": "liquidity", "type": "uint256" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price0CumulativeLast", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price1CumulativeLast", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "skim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "uint256", "name": "amount0Out", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1Out", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "swap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "sync", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token0", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token1", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/apps/uniswap-v2/contracts/ethers/UniswapFactory.ts b/src/apps/uniswap-v2/contracts/ethers/UniswapFactory.ts new file mode 100644 index 000000000..5874351cb --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/UniswapFactory.ts @@ -0,0 +1,229 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from 'ethers'; +import type { FunctionFragment, Result, EventFragment } from '@ethersproject/abi'; +import type { Listener, Provider } from '@ethersproject/providers'; +import type { TypedEventFilter, TypedEvent, TypedListener, OnEvent } from './common'; + +export interface UniswapFactoryInterface extends utils.Interface { + functions: { + 'allPairs(uint256)': FunctionFragment; + 'allPairsLength()': FunctionFragment; + 'createPair(address,address)': FunctionFragment; + 'feeTo()': FunctionFragment; + 'feeToSetter()': FunctionFragment; + 'getPair(address,address)': FunctionFragment; + 'setFeeTo(address)': FunctionFragment; + 'setFeeToSetter(address)': FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | 'allPairs' + | 'allPairsLength' + | 'createPair' + | 'feeTo' + | 'feeToSetter' + | 'getPair' + | 'setFeeTo' + | 'setFeeToSetter', + ): FunctionFragment; + + encodeFunctionData(functionFragment: 'allPairs', values: [BigNumberish]): string; + encodeFunctionData(functionFragment: 'allPairsLength', values?: undefined): string; + encodeFunctionData(functionFragment: 'createPair', values: [string, string]): string; + encodeFunctionData(functionFragment: 'feeTo', values?: undefined): string; + encodeFunctionData(functionFragment: 'feeToSetter', values?: undefined): string; + encodeFunctionData(functionFragment: 'getPair', values: [string, string]): string; + encodeFunctionData(functionFragment: 'setFeeTo', values: [string]): string; + encodeFunctionData(functionFragment: 'setFeeToSetter', values: [string]): string; + + decodeFunctionResult(functionFragment: 'allPairs', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'allPairsLength', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'createPair', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'feeTo', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'feeToSetter', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'getPair', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'setFeeTo', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'setFeeToSetter', data: BytesLike): Result; + + events: { + 'PairCreated(address,address,address,uint256)': EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: 'PairCreated'): EventFragment; +} + +export interface PairCreatedEventObject { + token0: string; + token1: string; + pair: string; + arg3: BigNumber; +} +export type PairCreatedEvent = TypedEvent<[string, string, string, BigNumber], PairCreatedEventObject>; + +export type PairCreatedEventFilter = TypedEventFilter; + +export interface UniswapFactory extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: UniswapFactoryInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined, + ): Promise>; + + listeners(eventFilter?: TypedEventFilter): Array>; + listeners(eventName?: string): Array; + removeAllListeners(eventFilter: TypedEventFilter): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + allPairs(arg0: BigNumberish, overrides?: CallOverrides): Promise<[string]>; + + allPairsLength(overrides?: CallOverrides): Promise<[BigNumber]>; + + createPair( + tokenA: string, + tokenB: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + feeTo(overrides?: CallOverrides): Promise<[string]>; + + feeToSetter(overrides?: CallOverrides): Promise<[string]>; + + getPair(arg0: string, arg1: string, overrides?: CallOverrides): Promise<[string]>; + + setFeeTo(_feeTo: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + setFeeToSetter( + _feeToSetter: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + }; + + allPairs(arg0: BigNumberish, overrides?: CallOverrides): Promise; + + allPairsLength(overrides?: CallOverrides): Promise; + + createPair( + tokenA: string, + tokenB: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + feeTo(overrides?: CallOverrides): Promise; + + feeToSetter(overrides?: CallOverrides): Promise; + + getPair(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + setFeeTo(_feeTo: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + setFeeToSetter( + _feeToSetter: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + callStatic: { + allPairs(arg0: BigNumberish, overrides?: CallOverrides): Promise; + + allPairsLength(overrides?: CallOverrides): Promise; + + createPair(tokenA: string, tokenB: string, overrides?: CallOverrides): Promise; + + feeTo(overrides?: CallOverrides): Promise; + + feeToSetter(overrides?: CallOverrides): Promise; + + getPair(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + setFeeTo(_feeTo: string, overrides?: CallOverrides): Promise; + + setFeeToSetter(_feeToSetter: string, overrides?: CallOverrides): Promise; + }; + + filters: { + 'PairCreated(address,address,address,uint256)'( + token0?: string | null, + token1?: string | null, + pair?: null, + arg3?: null, + ): PairCreatedEventFilter; + PairCreated(token0?: string | null, token1?: string | null, pair?: null, arg3?: null): PairCreatedEventFilter; + }; + + estimateGas: { + allPairs(arg0: BigNumberish, overrides?: CallOverrides): Promise; + + allPairsLength(overrides?: CallOverrides): Promise; + + createPair( + tokenA: string, + tokenB: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + feeTo(overrides?: CallOverrides): Promise; + + feeToSetter(overrides?: CallOverrides): Promise; + + getPair(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + setFeeTo(_feeTo: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + setFeeToSetter( + _feeToSetter: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + }; + + populateTransaction: { + allPairs(arg0: BigNumberish, overrides?: CallOverrides): Promise; + + allPairsLength(overrides?: CallOverrides): Promise; + + createPair( + tokenA: string, + tokenB: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + feeTo(overrides?: CallOverrides): Promise; + + feeToSetter(overrides?: CallOverrides): Promise; + + getPair(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + setFeeTo( + _feeTo: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + setFeeToSetter( + _feeToSetter: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + }; +} diff --git a/src/apps/uniswap-v2/contracts/ethers/UniswapPair.ts b/src/apps/uniswap-v2/contracts/ethers/UniswapPair.ts new file mode 100644 index 000000000..fa715bfba --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/UniswapPair.ts @@ -0,0 +1,721 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumber, + BigNumberish, + BytesLike, + CallOverrides, + ContractTransaction, + Overrides, + PopulatedTransaction, + Signer, + utils, +} from 'ethers'; +import type { FunctionFragment, Result, EventFragment } from '@ethersproject/abi'; +import type { Listener, Provider } from '@ethersproject/providers'; +import type { TypedEventFilter, TypedEvent, TypedListener, OnEvent } from './common'; + +export interface UniswapPairInterface extends utils.Interface { + functions: { + 'DOMAIN_SEPARATOR()': FunctionFragment; + 'MINIMUM_LIQUIDITY()': FunctionFragment; + 'PERMIT_TYPEHASH()': FunctionFragment; + 'allowance(address,address)': FunctionFragment; + 'approve(address,uint256)': FunctionFragment; + 'balanceOf(address)': FunctionFragment; + 'burn(address)': FunctionFragment; + 'decimals()': FunctionFragment; + 'factory()': FunctionFragment; + 'getReserves()': FunctionFragment; + 'initialize(address,address)': FunctionFragment; + 'kLast()': FunctionFragment; + 'mint(address)': FunctionFragment; + 'name()': FunctionFragment; + 'nonces(address)': FunctionFragment; + 'permit(address,address,uint256,uint256,uint8,bytes32,bytes32)': FunctionFragment; + 'price0CumulativeLast()': FunctionFragment; + 'price1CumulativeLast()': FunctionFragment; + 'skim(address)': FunctionFragment; + 'swap(uint256,uint256,address,bytes)': FunctionFragment; + 'symbol()': FunctionFragment; + 'sync()': FunctionFragment; + 'token0()': FunctionFragment; + 'token1()': FunctionFragment; + 'totalSupply()': FunctionFragment; + 'transfer(address,uint256)': FunctionFragment; + 'transferFrom(address,address,uint256)': FunctionFragment; + }; + + getFunction( + nameOrSignatureOrTopic: + | 'DOMAIN_SEPARATOR' + | 'MINIMUM_LIQUIDITY' + | 'PERMIT_TYPEHASH' + | 'allowance' + | 'approve' + | 'balanceOf' + | 'burn' + | 'decimals' + | 'factory' + | 'getReserves' + | 'initialize' + | 'kLast' + | 'mint' + | 'name' + | 'nonces' + | 'permit' + | 'price0CumulativeLast' + | 'price1CumulativeLast' + | 'skim' + | 'swap' + | 'symbol' + | 'sync' + | 'token0' + | 'token1' + | 'totalSupply' + | 'transfer' + | 'transferFrom', + ): FunctionFragment; + + encodeFunctionData(functionFragment: 'DOMAIN_SEPARATOR', values?: undefined): string; + encodeFunctionData(functionFragment: 'MINIMUM_LIQUIDITY', values?: undefined): string; + encodeFunctionData(functionFragment: 'PERMIT_TYPEHASH', values?: undefined): string; + encodeFunctionData(functionFragment: 'allowance', values: [string, string]): string; + encodeFunctionData(functionFragment: 'approve', values: [string, BigNumberish]): string; + encodeFunctionData(functionFragment: 'balanceOf', values: [string]): string; + encodeFunctionData(functionFragment: 'burn', values: [string]): string; + encodeFunctionData(functionFragment: 'decimals', values?: undefined): string; + encodeFunctionData(functionFragment: 'factory', values?: undefined): string; + encodeFunctionData(functionFragment: 'getReserves', values?: undefined): string; + encodeFunctionData(functionFragment: 'initialize', values: [string, string]): string; + encodeFunctionData(functionFragment: 'kLast', values?: undefined): string; + encodeFunctionData(functionFragment: 'mint', values: [string]): string; + encodeFunctionData(functionFragment: 'name', values?: undefined): string; + encodeFunctionData(functionFragment: 'nonces', values: [string]): string; + encodeFunctionData( + functionFragment: 'permit', + values: [string, string, BigNumberish, BigNumberish, BigNumberish, BytesLike, BytesLike], + ): string; + encodeFunctionData(functionFragment: 'price0CumulativeLast', values?: undefined): string; + encodeFunctionData(functionFragment: 'price1CumulativeLast', values?: undefined): string; + encodeFunctionData(functionFragment: 'skim', values: [string]): string; + encodeFunctionData(functionFragment: 'swap', values: [BigNumberish, BigNumberish, string, BytesLike]): string; + encodeFunctionData(functionFragment: 'symbol', values?: undefined): string; + encodeFunctionData(functionFragment: 'sync', values?: undefined): string; + encodeFunctionData(functionFragment: 'token0', values?: undefined): string; + encodeFunctionData(functionFragment: 'token1', values?: undefined): string; + encodeFunctionData(functionFragment: 'totalSupply', values?: undefined): string; + encodeFunctionData(functionFragment: 'transfer', values: [string, BigNumberish]): string; + encodeFunctionData(functionFragment: 'transferFrom', values: [string, string, BigNumberish]): string; + + decodeFunctionResult(functionFragment: 'DOMAIN_SEPARATOR', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'MINIMUM_LIQUIDITY', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'PERMIT_TYPEHASH', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'allowance', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'approve', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'balanceOf', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'burn', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'decimals', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'factory', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'getReserves', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'initialize', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'kLast', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'mint', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'name', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'nonces', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'permit', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'price0CumulativeLast', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'price1CumulativeLast', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'skim', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'swap', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'symbol', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'sync', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'token0', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'token1', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'totalSupply', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'transfer', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'transferFrom', data: BytesLike): Result; + + events: { + 'Approval(address,address,uint256)': EventFragment; + 'Burn(address,uint256,uint256,address)': EventFragment; + 'Mint(address,uint256,uint256)': EventFragment; + 'Swap(address,uint256,uint256,uint256,uint256,address)': EventFragment; + 'Sync(uint112,uint112)': EventFragment; + 'Transfer(address,address,uint256)': EventFragment; + }; + + getEvent(nameOrSignatureOrTopic: 'Approval'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'Burn'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'Mint'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'Swap'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'Sync'): EventFragment; + getEvent(nameOrSignatureOrTopic: 'Transfer'): EventFragment; +} + +export interface ApprovalEventObject { + owner: string; + spender: string; + value: BigNumber; +} +export type ApprovalEvent = TypedEvent<[string, string, BigNumber], ApprovalEventObject>; + +export type ApprovalEventFilter = TypedEventFilter; + +export interface BurnEventObject { + sender: string; + amount0: BigNumber; + amount1: BigNumber; + to: string; +} +export type BurnEvent = TypedEvent<[string, BigNumber, BigNumber, string], BurnEventObject>; + +export type BurnEventFilter = TypedEventFilter; + +export interface MintEventObject { + sender: string; + amount0: BigNumber; + amount1: BigNumber; +} +export type MintEvent = TypedEvent<[string, BigNumber, BigNumber], MintEventObject>; + +export type MintEventFilter = TypedEventFilter; + +export interface SwapEventObject { + sender: string; + amount0In: BigNumber; + amount1In: BigNumber; + amount0Out: BigNumber; + amount1Out: BigNumber; + to: string; +} +export type SwapEvent = TypedEvent<[string, BigNumber, BigNumber, BigNumber, BigNumber, string], SwapEventObject>; + +export type SwapEventFilter = TypedEventFilter; + +export interface SyncEventObject { + reserve0: BigNumber; + reserve1: BigNumber; +} +export type SyncEvent = TypedEvent<[BigNumber, BigNumber], SyncEventObject>; + +export type SyncEventFilter = TypedEventFilter; + +export interface TransferEventObject { + from: string; + to: string; + value: BigNumber; +} +export type TransferEvent = TypedEvent<[string, string, BigNumber], TransferEventObject>; + +export type TransferEventFilter = TypedEventFilter; + +export interface UniswapPair extends BaseContract { + connect(signerOrProvider: Signer | Provider | string): this; + attach(addressOrName: string): this; + deployed(): Promise; + + interface: UniswapPairInterface; + + queryFilter( + event: TypedEventFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined, + ): Promise>; + + listeners(eventFilter?: TypedEventFilter): Array>; + listeners(eventName?: string): Array; + removeAllListeners(eventFilter: TypedEventFilter): this; + removeAllListeners(eventName?: string): this; + off: OnEvent; + on: OnEvent; + once: OnEvent; + removeListener: OnEvent; + + functions: { + DOMAIN_SEPARATOR(overrides?: CallOverrides): Promise<[string]>; + + MINIMUM_LIQUIDITY(overrides?: CallOverrides): Promise<[BigNumber]>; + + PERMIT_TYPEHASH(overrides?: CallOverrides): Promise<[string]>; + + allowance(arg0: string, arg1: string, overrides?: CallOverrides): Promise<[BigNumber]>; + + approve( + spender: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + balanceOf(arg0: string, overrides?: CallOverrides): Promise<[BigNumber]>; + + burn(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + decimals(overrides?: CallOverrides): Promise<[number]>; + + factory(overrides?: CallOverrides): Promise<[string]>; + + getReserves(overrides?: CallOverrides): Promise< + [BigNumber, BigNumber, number] & { + _reserve0: BigNumber; + _reserve1: BigNumber; + _blockTimestampLast: number; + } + >; + + initialize( + _token0: string, + _token1: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + kLast(overrides?: CallOverrides): Promise<[BigNumber]>; + + mint(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + name(overrides?: CallOverrides): Promise<[string]>; + + nonces(arg0: string, overrides?: CallOverrides): Promise<[BigNumber]>; + + permit( + owner: string, + spender: string, + value: BigNumberish, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + price0CumulativeLast(overrides?: CallOverrides): Promise<[BigNumber]>; + + price1CumulativeLast(overrides?: CallOverrides): Promise<[BigNumber]>; + + skim(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + swap( + amount0Out: BigNumberish, + amount1Out: BigNumberish, + to: string, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + symbol(overrides?: CallOverrides): Promise<[string]>; + + sync(overrides?: Overrides & { from?: string | Promise }): Promise; + + token0(overrides?: CallOverrides): Promise<[string]>; + + token1(overrides?: CallOverrides): Promise<[string]>; + + totalSupply(overrides?: CallOverrides): Promise<[BigNumber]>; + + transfer( + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + transferFrom( + from: string, + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + }; + + DOMAIN_SEPARATOR(overrides?: CallOverrides): Promise; + + MINIMUM_LIQUIDITY(overrides?: CallOverrides): Promise; + + PERMIT_TYPEHASH(overrides?: CallOverrides): Promise; + + allowance(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + approve( + spender: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + balanceOf(arg0: string, overrides?: CallOverrides): Promise; + + burn(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + decimals(overrides?: CallOverrides): Promise; + + factory(overrides?: CallOverrides): Promise; + + getReserves(overrides?: CallOverrides): Promise< + [BigNumber, BigNumber, number] & { + _reserve0: BigNumber; + _reserve1: BigNumber; + _blockTimestampLast: number; + } + >; + + initialize( + _token0: string, + _token1: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + kLast(overrides?: CallOverrides): Promise; + + mint(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + name(overrides?: CallOverrides): Promise; + + nonces(arg0: string, overrides?: CallOverrides): Promise; + + permit( + owner: string, + spender: string, + value: BigNumberish, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + price0CumulativeLast(overrides?: CallOverrides): Promise; + + price1CumulativeLast(overrides?: CallOverrides): Promise; + + skim(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + swap( + amount0Out: BigNumberish, + amount1Out: BigNumberish, + to: string, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + symbol(overrides?: CallOverrides): Promise; + + sync(overrides?: Overrides & { from?: string | Promise }): Promise; + + token0(overrides?: CallOverrides): Promise; + + token1(overrides?: CallOverrides): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + transferFrom( + from: string, + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + callStatic: { + DOMAIN_SEPARATOR(overrides?: CallOverrides): Promise; + + MINIMUM_LIQUIDITY(overrides?: CallOverrides): Promise; + + PERMIT_TYPEHASH(overrides?: CallOverrides): Promise; + + allowance(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + approve(spender: string, value: BigNumberish, overrides?: CallOverrides): Promise; + + balanceOf(arg0: string, overrides?: CallOverrides): Promise; + + burn( + to: string, + overrides?: CallOverrides, + ): Promise<[BigNumber, BigNumber] & { amount0: BigNumber; amount1: BigNumber }>; + + decimals(overrides?: CallOverrides): Promise; + + factory(overrides?: CallOverrides): Promise; + + getReserves(overrides?: CallOverrides): Promise< + [BigNumber, BigNumber, number] & { + _reserve0: BigNumber; + _reserve1: BigNumber; + _blockTimestampLast: number; + } + >; + + initialize(_token0: string, _token1: string, overrides?: CallOverrides): Promise; + + kLast(overrides?: CallOverrides): Promise; + + mint(to: string, overrides?: CallOverrides): Promise; + + name(overrides?: CallOverrides): Promise; + + nonces(arg0: string, overrides?: CallOverrides): Promise; + + permit( + owner: string, + spender: string, + value: BigNumberish, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: CallOverrides, + ): Promise; + + price0CumulativeLast(overrides?: CallOverrides): Promise; + + price1CumulativeLast(overrides?: CallOverrides): Promise; + + skim(to: string, overrides?: CallOverrides): Promise; + + swap( + amount0Out: BigNumberish, + amount1Out: BigNumberish, + to: string, + data: BytesLike, + overrides?: CallOverrides, + ): Promise; + + symbol(overrides?: CallOverrides): Promise; + + sync(overrides?: CallOverrides): Promise; + + token0(overrides?: CallOverrides): Promise; + + token1(overrides?: CallOverrides): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer(to: string, value: BigNumberish, overrides?: CallOverrides): Promise; + + transferFrom(from: string, to: string, value: BigNumberish, overrides?: CallOverrides): Promise; + }; + + filters: { + 'Approval(address,address,uint256)'( + owner?: string | null, + spender?: string | null, + value?: null, + ): ApprovalEventFilter; + Approval(owner?: string | null, spender?: string | null, value?: null): ApprovalEventFilter; + + 'Burn(address,uint256,uint256,address)'( + sender?: string | null, + amount0?: null, + amount1?: null, + to?: string | null, + ): BurnEventFilter; + Burn(sender?: string | null, amount0?: null, amount1?: null, to?: string | null): BurnEventFilter; + + 'Mint(address,uint256,uint256)'(sender?: string | null, amount0?: null, amount1?: null): MintEventFilter; + Mint(sender?: string | null, amount0?: null, amount1?: null): MintEventFilter; + + 'Swap(address,uint256,uint256,uint256,uint256,address)'( + sender?: string | null, + amount0In?: null, + amount1In?: null, + amount0Out?: null, + amount1Out?: null, + to?: string | null, + ): SwapEventFilter; + Swap( + sender?: string | null, + amount0In?: null, + amount1In?: null, + amount0Out?: null, + amount1Out?: null, + to?: string | null, + ): SwapEventFilter; + + 'Sync(uint112,uint112)'(reserve0?: null, reserve1?: null): SyncEventFilter; + Sync(reserve0?: null, reserve1?: null): SyncEventFilter; + + 'Transfer(address,address,uint256)'(from?: string | null, to?: string | null, value?: null): TransferEventFilter; + Transfer(from?: string | null, to?: string | null, value?: null): TransferEventFilter; + }; + + estimateGas: { + DOMAIN_SEPARATOR(overrides?: CallOverrides): Promise; + + MINIMUM_LIQUIDITY(overrides?: CallOverrides): Promise; + + PERMIT_TYPEHASH(overrides?: CallOverrides): Promise; + + allowance(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + approve( + spender: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + balanceOf(arg0: string, overrides?: CallOverrides): Promise; + + burn(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + decimals(overrides?: CallOverrides): Promise; + + factory(overrides?: CallOverrides): Promise; + + getReserves(overrides?: CallOverrides): Promise; + + initialize( + _token0: string, + _token1: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + kLast(overrides?: CallOverrides): Promise; + + mint(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + name(overrides?: CallOverrides): Promise; + + nonces(arg0: string, overrides?: CallOverrides): Promise; + + permit( + owner: string, + spender: string, + value: BigNumberish, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + price0CumulativeLast(overrides?: CallOverrides): Promise; + + price1CumulativeLast(overrides?: CallOverrides): Promise; + + skim(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + swap( + amount0Out: BigNumberish, + amount1Out: BigNumberish, + to: string, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + symbol(overrides?: CallOverrides): Promise; + + sync(overrides?: Overrides & { from?: string | Promise }): Promise; + + token0(overrides?: CallOverrides): Promise; + + token1(overrides?: CallOverrides): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + transferFrom( + from: string, + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + }; + + populateTransaction: { + DOMAIN_SEPARATOR(overrides?: CallOverrides): Promise; + + MINIMUM_LIQUIDITY(overrides?: CallOverrides): Promise; + + PERMIT_TYPEHASH(overrides?: CallOverrides): Promise; + + allowance(arg0: string, arg1: string, overrides?: CallOverrides): Promise; + + approve( + spender: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + balanceOf(arg0: string, overrides?: CallOverrides): Promise; + + burn(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + decimals(overrides?: CallOverrides): Promise; + + factory(overrides?: CallOverrides): Promise; + + getReserves(overrides?: CallOverrides): Promise; + + initialize( + _token0: string, + _token1: string, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + kLast(overrides?: CallOverrides): Promise; + + mint(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + name(overrides?: CallOverrides): Promise; + + nonces(arg0: string, overrides?: CallOverrides): Promise; + + permit( + owner: string, + spender: string, + value: BigNumberish, + deadline: BigNumberish, + v: BigNumberish, + r: BytesLike, + s: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + price0CumulativeLast(overrides?: CallOverrides): Promise; + + price1CumulativeLast(overrides?: CallOverrides): Promise; + + skim(to: string, overrides?: Overrides & { from?: string | Promise }): Promise; + + swap( + amount0Out: BigNumberish, + amount1Out: BigNumberish, + to: string, + data: BytesLike, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + symbol(overrides?: CallOverrides): Promise; + + sync(overrides?: Overrides & { from?: string | Promise }): Promise; + + token0(overrides?: CallOverrides): Promise; + + token1(overrides?: CallOverrides): Promise; + + totalSupply(overrides?: CallOverrides): Promise; + + transfer( + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + + transferFrom( + from: string, + to: string, + value: BigNumberish, + overrides?: Overrides & { from?: string | Promise }, + ): Promise; + }; +} diff --git a/src/apps/uniswap-v2/contracts/ethers/common.ts b/src/apps/uniswap-v2/contracts/ethers/common.ts new file mode 100644 index 000000000..6cfb10425 --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/common.ts @@ -0,0 +1,30 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { Listener } from '@ethersproject/providers'; +import type { Event, EventFilter } from 'ethers'; + +export interface TypedEvent = any, TArgsObject = any> extends Event { + args: TArgsArray & TArgsObject; +} + +export interface TypedEventFilter<_TEvent extends TypedEvent> extends EventFilter {} + +export interface TypedListener { + (...listenerArg: [...__TypechainArgsArray, TEvent]): void; +} + +type __TypechainArgsArray = T extends TypedEvent ? U : never; + +export interface OnEvent { + (eventFilter: TypedEventFilter, listener: TypedListener): TRes; + (eventName: string, listener: Listener): TRes; +} + +export type MinEthersFactory = { + deploy(...a: ARGS[]): Promise; +}; + +export type GetContractTypeFromFactory = F extends MinEthersFactory ? C : never; + +export type GetARGsTypeFromFactory = F extends MinEthersFactory ? Parameters : never; diff --git a/src/apps/uniswap-v2/contracts/ethers/factories/UniswapFactory__factory.ts b/src/apps/uniswap-v2/contracts/ethers/factories/UniswapFactory__factory.ts new file mode 100644 index 000000000..1d28af28b --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/factories/UniswapFactory__factory.ts @@ -0,0 +1,211 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from 'ethers'; +import type { Provider } from '@ethersproject/providers'; +import type { UniswapFactory, UniswapFactoryInterface } from '../UniswapFactory'; + +const _abi = [ + { + inputs: [ + { + internalType: 'address', + name: '_feeToSetter', + type: 'address', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'token0', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token1', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'pair', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'PairCreated', + type: 'event', + }, + { + constant: true, + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'allPairs', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'allPairsLength', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'tokenA', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenB', + type: 'address', + }, + ], + name: 'createPair', + outputs: [ + { + internalType: 'address', + name: 'pair', + type: 'address', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'feeTo', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'feeToSetter', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'getPair', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: '_feeTo', + type: 'address', + }, + ], + name: 'setFeeTo', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: '_feeToSetter', + type: 'address', + }, + ], + name: 'setFeeToSetter', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, +]; + +export class UniswapFactory__factory { + static readonly abi = _abi; + static createInterface(): UniswapFactoryInterface { + return new utils.Interface(_abi) as UniswapFactoryInterface; + } + static connect(address: string, signerOrProvider: Signer | Provider): UniswapFactory { + return new Contract(address, _abi, signerOrProvider) as UniswapFactory; + } +} diff --git a/src/apps/uniswap-v2/contracts/ethers/factories/UniswapPair__factory.ts b/src/apps/uniswap-v2/contracts/ethers/factories/UniswapPair__factory.ts new file mode 100644 index 000000000..79a8745be --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/factories/UniswapPair__factory.ts @@ -0,0 +1,731 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Signer, utils } from 'ethers'; +import type { Provider } from '@ethersproject/providers'; +import type { UniswapPair, UniswapPairInterface } from '../UniswapPair'; + +const _abi = [ + { + inputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'Burn', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Mint', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0In', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1In', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0Out', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1Out', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'Swap', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint112', + name: 'reserve0', + type: 'uint112', + }, + { + indexed: false, + internalType: 'uint112', + name: 'reserve1', + type: 'uint112', + }, + ], + name: 'Sync', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + constant: true, + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'MINIMUM_LIQUIDITY', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'PERMIT_TYPEHASH', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'burn', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'getReserves', + outputs: [ + { + internalType: 'uint112', + name: '_reserve0', + type: 'uint112', + }, + { + internalType: 'uint112', + name: '_reserve1', + type: 'uint112', + }, + { + internalType: 'uint32', + name: '_blockTimestampLast', + type: 'uint32', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: '_token0', + type: 'address', + }, + { + internalType: 'address', + name: '_token1', + type: 'address', + }, + ], + name: 'initialize', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'kLast', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'mint', + outputs: [ + { + internalType: 'uint256', + name: 'liquidity', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'nonces', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'permit', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'price0CumulativeLast', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'price1CumulativeLast', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'skim', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'amount0Out', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1Out', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'swap', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [], + name: 'sync', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'token0', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'token1', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, +]; + +export class UniswapPair__factory { + static readonly abi = _abi; + static createInterface(): UniswapPairInterface { + return new utils.Interface(_abi) as UniswapPairInterface; + } + static connect(address: string, signerOrProvider: Signer | Provider): UniswapPair { + return new Contract(address, _abi, signerOrProvider) as UniswapPair; + } +} diff --git a/src/apps/uniswap-v2/contracts/ethers/factories/index.ts b/src/apps/uniswap-v2/contracts/ethers/factories/index.ts new file mode 100644 index 000000000..3ba8ee101 --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/factories/index.ts @@ -0,0 +1,5 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { UniswapFactory__factory } from './UniswapFactory__factory'; +export { UniswapPair__factory } from './UniswapPair__factory'; diff --git a/src/apps/uniswap-v2/contracts/ethers/index.ts b/src/apps/uniswap-v2/contracts/ethers/index.ts new file mode 100644 index 000000000..fd62acfec --- /dev/null +++ b/src/apps/uniswap-v2/contracts/ethers/index.ts @@ -0,0 +1,8 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { UniswapFactory } from './UniswapFactory'; +export type { UniswapPair } from './UniswapPair'; +export * as factories from './factories'; +export { UniswapFactory__factory } from './factories/UniswapFactory__factory'; +export { UniswapPair__factory } from './factories/UniswapPair__factory'; diff --git a/src/apps/uniswap-v2/contracts/index.ts b/src/apps/uniswap-v2/contracts/index.ts new file mode 100644 index 000000000..07c025110 --- /dev/null +++ b/src/apps/uniswap-v2/contracts/index.ts @@ -0,0 +1,28 @@ +import { Injectable, Inject } from '@nestjs/common'; + +import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; +import { ContractFactory } from '~contract/contracts'; +import { Network } from '~types/network.interface'; + +import { UniswapFactory__factory } from './ethers'; +import { UniswapPair__factory } from './ethers'; + +// eslint-disable-next-line +type ContractOpts = { address: string; network: Network }; + +@Injectable() +export class UniswapV2ContractFactory extends ContractFactory { + constructor(@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit) { + super((network: Network) => appToolkit.getNetworkProvider(network)); + } + + uniswapFactory({ address, network }: ContractOpts) { + return UniswapFactory__factory.connect(address, this.appToolkit.getNetworkProvider(network)); + } + uniswapPair({ address, network }: ContractOpts) { + return UniswapPair__factory.connect(address, this.appToolkit.getNetworkProvider(network)); + } +} + +export type { UniswapFactory } from './ethers'; +export type { UniswapPair } from './ethers'; diff --git a/src/apps/uniswap-v2/ethereum/uniswap-v2.balance-fetcher.ts b/src/apps/uniswap-v2/ethereum/uniswap-v2.balance-fetcher.ts new file mode 100644 index 000000000..29e72f30f --- /dev/null +++ b/src/apps/uniswap-v2/ethereum/uniswap-v2.balance-fetcher.ts @@ -0,0 +1,44 @@ +import { Inject } from '@nestjs/common'; + +import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; +import { Register } from '~app-toolkit/decorators'; +import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present'; +import { BalanceFetcher } from '~balance/balance-fetcher.interface'; +import { Network } from '~types/network.interface'; + +import { UniswapV2TheGraphPoolTokenBalanceHelper } from '../helpers/uniswap-v2.the-graph.pool-token-balance-helper'; +import UNISWAP_V2_DEFINITION from '../uniswap-v2.definition'; + +const appId = UNISWAP_V2_DEFINITION.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.BalanceFetcher(appId, network) +export class EthereumUniswapV2BalanceFetcher implements BalanceFetcher { + constructor( + @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, + @Inject(UniswapV2TheGraphPoolTokenBalanceHelper) + private readonly uniswapV2TheGraphPoolTokenBalanceHelper: UniswapV2TheGraphPoolTokenBalanceHelper, + ) {} + + private getPoolTokenBalances(address: string) { + return this.uniswapV2TheGraphPoolTokenBalanceHelper.getBalances({ + appId, + groupId: UNISWAP_V2_DEFINITION.groups.pool.id, + network, + address, + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2', + symbolPrefix: 'UNI-V2', + }); + } + + async getBalances(address: string) { + const balances = await this.getPoolTokenBalances(address); + + return presentBalanceFetcherResponse([ + { + label: 'Pools', + assets: balances, + }, + ]); + } +} diff --git a/src/apps/uniswap-v2/ethereum/uniswap-v2.pool.token-fetcher.ts b/src/apps/uniswap-v2/ethereum/uniswap-v2.pool.token-fetcher.ts new file mode 100644 index 000000000..d08457799 --- /dev/null +++ b/src/apps/uniswap-v2/ethereum/uniswap-v2.pool.token-fetcher.ts @@ -0,0 +1,90 @@ +import { Inject } from '@nestjs/common'; + +import { Register } from '~app-toolkit/decorators'; +import { PositionFetcher } from '~position/position-fetcher.interface'; +import { AppTokenPosition } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import { UniswapFactory, UniswapPair, UniswapV2ContractFactory } from '../contracts'; +import { UniswapV2PoolTokenHelper } from '../helpers/uniswap-v2.pool.token-helper'; +import { UniswapV2TheGraphPoolTokenAddressStrategy } from '../helpers/uniswap-v2.the-graph.pool-token-address-strategy'; +import { UniswapV2TheGraphPoolVolumeStrategy } from '../helpers/uniswap-v2.the-graph.pool-volume-strategy'; +import UNISWAP_V2_DEFINITION from '../uniswap-v2.definition'; + +const appId = UNISWAP_V2_DEFINITION.id; +const groupId = UNISWAP_V2_DEFINITION.groups.pool.id; +const network = Network.ETHEREUM_MAINNET; + +@Register.TokenPositionFetcher({ appId, groupId, network }) +export class EthereumUniswapV2PoolTokenFetcher implements PositionFetcher { + constructor( + @Inject(UniswapV2ContractFactory) + private readonly uniswapV2ContractFactory: UniswapV2ContractFactory, + @Inject(UniswapV2PoolTokenHelper) + private readonly uniswapV2PoolTokenHelper: UniswapV2PoolTokenHelper, + @Inject(UniswapV2TheGraphPoolTokenAddressStrategy) + private readonly uniswapV2TheGraphPoolTokenAddressStrategy: UniswapV2TheGraphPoolTokenAddressStrategy, + @Inject(UniswapV2TheGraphPoolVolumeStrategy) + private readonly uniswapV2TheGraphPoolVolumeStrategy: UniswapV2TheGraphPoolVolumeStrategy, + ) {} + + async getPositions() { + return this.uniswapV2PoolTokenHelper.getTokens({ + network, + appId, + groupId: UNISWAP_V2_DEFINITION.groups.pool.id, + factoryAddress: '0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f', + hiddenTokens: ['0x62359ed7505efc61ff1d56fef82158ccaffa23d7', '0x35bd01fc9d6d5d81ca9e055db88dc49aa2c699a8'], + blockedPools: ['0x9cbfb60a09a9a33a10312da0f39977cbdb7fde23'], // Uniswap V2: SAITAMA - has a transfer fee (not supported by our zap) + appTokenDependencies: [ + { + appId: 'alpha-v1', + groupIds: ['lending'], + network, + }, + ], + resolveFactoryContract: ({ address, network }) => + this.uniswapV2ContractFactory.uniswapFactory({ address, network }), + resolvePoolContract: ({ address, network }) => this.uniswapV2ContractFactory.uniswapPair({ address, network }), + resolvePoolTokenAddresses: this.uniswapV2TheGraphPoolTokenAddressStrategy.build({ + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2', + first: 1000, + requiredPools: [ + '0xaad22f5543fcdaa694b68f94be177b561836ae57', // sUSD-$BASED + '0xe98f89a2b3aecdbe2118202826478eb02434459a', // DAI-DEBASE + '0x980a07e4f64d21a0cb2ef8d4af362a79b9f5c0da', // DAI-BSGS + '0xf58d2bacbc68c587730ea0ce5131f6ae7c622a5d', // ORCL5-ETH + '0xc3601f3e1c26d1a47571c559348e4156786d1fec', // DUCK-WETH + '0xcadd30b39f01cfdfb848174b19bbb5b1b7486159', // DSU-ESS + '0x0bf46ba06dc1d33c3bd80ff42497ebff13a88900', // rDPX- ETH + '0xfd0a40bc83c5fae4203dec7e5929b446b07d1c76', // FRAX-ETH + '0x97c4adc5d28a86f9470c70dd91dc6cc2f20d2d4d', // FRAX-USDC + ], + }), + resolvePoolTokenSymbol: ({ multicall, poolContract }) => multicall.wrap(poolContract).symbol(), + resolvePoolTokenSupply: ({ multicall, poolContract }) => multicall.wrap(poolContract).totalSupply(), + resolvePoolReserves: async ({ multicall, poolContract }) => + multicall + .wrap(poolContract) + .getReserves() + .then(v => [v[0], v[1]]), + resolvePoolUnderlyingTokenAddresses: async ({ multicall, poolContract }) => + Promise.all([multicall.wrap(poolContract).token0(), multicall.wrap(poolContract).token1()]), + resolveTokenDisplaySymbol: token => (token.symbol === 'WETH' ? 'ETH' : token.symbol), + resolvePoolVolumes: this.uniswapV2TheGraphPoolVolumeStrategy.build({ + subgraphUrl: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2', + first: 1000, + requiredPools: [ + '0xaad22f5543fcdaa694b68f94be177b561836ae57', // sUSD-$BASED + '0xe98f89a2b3aecdbe2118202826478eb02434459a', // DAI-DEBASE + '0x980a07e4f64d21a0cb2ef8d4af362a79b9f5c0da', // DAI-BSGS + '0xf58d2bacbc68c587730ea0ce5131f6ae7c622a5d', // ORCL5-ETH + '0xc3601f3e1c26d1a47571c559348e4156786d1fec', // DUCK-WETH + '0xcadd30b39f01cfdfb848174b19bbb5b1b7486159', // DSU-ESS + '0x0bf46ba06dc1d33c3bd80ff42497ebff13a88900', // rDPX- ETH + '0xfd0a40bc83c5fae4203dec7e5929b446b07d1c76', // FRAX-ETH + ], + }), + }); + } +} diff --git a/src/apps/uniswap-v2/helpers/uniswap-v2.on-chain.pool-token-address-strategy.ts b/src/apps/uniswap-v2/helpers/uniswap-v2.on-chain.pool-token-address-strategy.ts new file mode 100644 index 000000000..8c7a7f1da --- /dev/null +++ b/src/apps/uniswap-v2/helpers/uniswap-v2.on-chain.pool-token-address-strategy.ts @@ -0,0 +1,48 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { BigNumberish } from 'ethers'; +import _ from 'lodash'; + +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { EthersMulticall as Multicall } from '~multicall/multicall.ethers'; + +import { UniswapFactory, UniswapPair } from '../contracts'; + +import { UniswapV2PoolTokenHelperParams } from './uniswap-v2.pool.token-helper'; + +type GetPoolAddressListParams = { + resolvePoolsLength: (opts: { multicall: Multicall; factoryContract: T }) => Promise; + resolvePoolAddress: (opts: { multicall: Multicall; factoryContract: T; poolIndex: number }) => Promise; + resolvePoolAddressIsValid?: (opts: { multicall: Multicall; poolContract: V }) => Promise; +}; + +@Injectable() +export class UniswapV2OnChainPoolTokenAddressStrategy { + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} + + build({ + resolvePoolsLength, + resolvePoolAddress, + resolvePoolAddressIsValid, + }: GetPoolAddressListParams): UniswapV2PoolTokenHelperParams['resolvePoolTokenAddresses'] { + return async ({ factoryAddress, network, resolveFactoryContract, resolvePoolContract }) => { + const multicall = this.appToolkit.getMulticall(network); + + const factoryContract = resolveFactoryContract({ address: factoryAddress, network }); + const poolsLength = await resolvePoolsLength({ multicall, factoryContract }); + + const poolAddresses = await Promise.all( + _.range(0, Number(poolsLength)).map(async poolIndex => { + const poolAddressRaw = await resolvePoolAddress({ multicall, factoryContract, poolIndex }); + const poolAddress = poolAddressRaw.toLowerCase(); + if (!resolvePoolAddressIsValid) return poolAddress; + + const poolContract = resolvePoolContract({ address: poolAddress, network }); + const isValid = await resolvePoolAddressIsValid({ multicall, poolContract }); + return isValid ? poolAddress : null; + }), + ); + + return _.compact(poolAddresses); + }; + } +} diff --git a/src/apps/uniswap-v2/helpers/uniswap-v2.pool.token-helper.ts b/src/apps/uniswap-v2/helpers/uniswap-v2.pool.token-helper.ts new file mode 100644 index 000000000..9759e20fd --- /dev/null +++ b/src/apps/uniswap-v2/helpers/uniswap-v2.pool.token-helper.ts @@ -0,0 +1,200 @@ +import { Inject } from '@nestjs/common'; +import { BigNumberish } from 'ethers'; +import _ from 'lodash'; +import { keyBy, sortBy } from 'lodash'; + +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { + buildDollarDisplayItem, + buildPercentageDisplayItem, +} from '~app-toolkit/helpers/presentation/display-item.present'; +import { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present'; +import { EthersMulticall as Multicall } from '~multicall/multicall.ethers'; +import { ContractType } from '~position/contract.interface'; +import { AppTokenPosition, Token } from '~position/position.interface'; +import { AppGroupsDefinition } from '~position/position.service'; +import { Network } from '~types/network.interface'; + +import { UniswapFactory, UniswapPair } from '../contracts'; + +export type UniswapV2PoolTokenDataProps = { + liquidity: number; + fee: number; + volume: number; + volumeChangePercentage: number; + isBlocked: boolean; +}; + +type ResolvePoolVolumesResponse = { poolAddress: string; volumeChangeUSD: number; volumeChangePercentage: number }[]; + +type ResolvePoolTokenAddressesResponse = string[]; + +export type UniswapV2PoolTokenHelperParams = { + network: Network; + appId: string; + groupId: string; + factoryAddress: string; + fee?: number; + minLiquidity?: number; + hiddenTokens?: string[]; + blockedPools?: string[]; + appTokenDependencies?: AppGroupsDefinition[]; + resolveFactoryContract(opts: { address: string; network: Network }): T; + resolvePoolContract(opts: { address: string; network: Network }): V; + resolvePoolTokenAddresses: (opts: { + appId: string; + network: Network; + factoryAddress: string; + resolveFactoryContract(opts: { address: string; network: Network }): T; + resolvePoolContract(opts: { address: string; network: Network }): V; + }) => Promise; + resolvePoolVolumes?: (opts: { + appId: string; + network: Network; + resolveFactoryContract(opts: { address: string; network: Network }): T; + resolvePoolContract(opts: { address: string; network: Network }): V; + }) => Promise; + resolvePoolTokenSymbol(opts: { multicall: Multicall; poolContract: V }): Promise; + resolvePoolTokenSupply(opts: { multicall: Multicall; poolContract: V }): Promise; + resolvePoolUnderlyingTokenAddresses(opts: { multicall: Multicall; poolContract: V }): Promise<[string, string]>; + resolvePoolReserves(opts: { multicall: Multicall; poolContract: V }): Promise<[BigNumberish, BigNumberish]>; + resolveTokenDisplayPrefix?: (symbol: string) => string; + resolveTokenDisplaySymbol?: (token: Token) => string; +}; + +export class UniswapV2PoolTokenHelper { + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} + + async getTokens({ + network, + appId, + groupId, + factoryAddress, + fee = 0.003, + minLiquidity = 0, + hiddenTokens = [], + blockedPools = [], + appTokenDependencies = [], + resolveFactoryContract, + resolvePoolContract, + resolvePoolTokenAddresses, + resolvePoolTokenSymbol, + resolvePoolTokenSupply, + resolvePoolUnderlyingTokenAddresses, + resolvePoolReserves, + resolveTokenDisplayPrefix = symbol => symbol, + resolveTokenDisplaySymbol = token => token.symbol, + resolvePoolVolumes = async () => [], + }: UniswapV2PoolTokenHelperParams) { + const multicall = this.appToolkit.getMulticall(network); + + const baseTokens = await this.appToolkit.getBaseTokenPrices(network); + + const appTokens = await this.appToolkit.getAppTokenPositions(...appTokenDependencies); + const baseTokensByAddress = keyBy(baseTokens, p => p.address); + const appTokensByAddress = keyBy(appTokens, p => p.address); + + const poolAddresses: ResolvePoolTokenAddressesResponse = await resolvePoolTokenAddresses({ + appId, + network, + factoryAddress, + resolveFactoryContract, + resolvePoolContract, + }).catch(() => []); + + const poolVolumes: ResolvePoolVolumesResponse = await resolvePoolVolumes({ + appId, + network, + resolveFactoryContract, + resolvePoolContract, + }).catch(() => []); + + const poolStatsRaw = await Promise.all( + poolAddresses.map(async address => { + const type = ContractType.APP_TOKEN; + const poolContract = resolvePoolContract({ address, network }); + const [token0AddressRaw, token1AddressRaw] = await resolvePoolUnderlyingTokenAddresses({ + multicall, + poolContract, + }); + + const token0Address = token0AddressRaw.toLowerCase(); + const token1Address = token1AddressRaw.toLowerCase(); + if (hiddenTokens.includes(token0Address) || hiddenTokens.includes(token1Address)) return null; + + const token0 = appTokensByAddress[token0Address] ?? baseTokensByAddress[token0Address]; + const token1 = appTokensByAddress[token1Address] ?? baseTokensByAddress[token1Address]; + if (!token0 || !token1) return null; + + // Retrieve pool reserves and pool token supply + const [symbol, supplyRaw, reservesRaw] = await Promise.all([ + resolvePoolTokenSymbol({ multicall, poolContract }), + resolvePoolTokenSupply({ multicall, poolContract }), + resolvePoolReserves({ multicall, poolContract }), + ]); + + // Data Props + const decimals = 18; + const tokens = [token0, token1]; + const reserves = reservesRaw.map((r, i) => Number(r) / 10 ** tokens[i].decimals); + const liquidity = tokens[0].price * reserves[0] + tokens[1].price * reserves[1]; + const reservePercentages = tokens.map((t, i) => reserves[i] * (t.price / liquidity)); + const supply = Number(supplyRaw) / 10 ** decimals; + const price = liquidity / supply; + const pricePerShare = reserves.map(r => r / supply); + const volume = poolVolumes.find(v => v.poolAddress === address)?.volumeChangeUSD ?? 0; + const volumeChangePercentage = poolVolumes.find(v => v.poolAddress === address)?.volumeChangePercentage ?? 0; + const isBlocked = blockedPools.includes(address); + + // Display Props + const prefix = resolveTokenDisplayPrefix(symbol); + const label = `${prefix} ${resolveTokenDisplaySymbol(token0)} / ${resolveTokenDisplaySymbol(token1)}`; + const secondaryLabel = reservePercentages.map(p => `${Math.round(p * 100)}%`).join(' / '); + const images = tokens.map(v => getImagesFromToken(v)).flat(); + const statsItems = [ + { label: 'Liquidity', value: buildDollarDisplayItem(liquidity) }, + { label: 'Volume', value: buildDollarDisplayItem(volume) }, + { label: 'Fee', value: buildPercentageDisplayItem(fee) }, + ]; + + const poolToken: AppTokenPosition = { + type, + network, + address, + decimals, + symbol, + supply, + appId, + groupId, + price, + pricePerShare, + tokens, + + displayProps: { + label, + secondaryLabel, + images, + statsItems, + }, + + dataProps: { + liquidity, + fee, + volume, + volumeChangePercentage, + isBlocked, + }, + }; + + return poolToken; + }), + ); + + const poolStats = _.compact(poolStatsRaw); + + return sortBy( + poolStats.filter(t => !!t && t.dataProps.liquidity > minLiquidity), + t => -t!.dataProps.liquidity, + ); + } +} diff --git a/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-address-strategy.ts b/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-address-strategy.ts new file mode 100644 index 000000000..4ed4c52bb --- /dev/null +++ b/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-address-strategy.ts @@ -0,0 +1,93 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { gql } from 'graphql-request'; +import { uniq } from 'lodash'; + +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { Cache } from '~cache/cache.decorator'; +import { Network } from '~types/network.interface'; + +import { UniswapFactory, UniswapPair } from '../contracts'; + +import { UniswapV2PoolTokenHelperParams } from './uniswap-v2.pool.token-helper'; + +const DEFAULT_POOLS_QUERY = gql` + query getPools($first: Int) { + pairs(first: $first, skip: 0, orderBy: reserveUSD, orderDirection: desc) { + id + } + } +`; + +const DEFAULT_POOLS_BY_ID_QUERY = gql` + query getPoolsById($ids: [ID!]) { + pairs(where: { id_in: $ids }) { + id + } + } +`; + +type PoolsResponse = { + pairs?: { + id: string; + }[]; +}; + +type UniswapV2TheGraphPoolTokenAddressStrategyParams = { + subgraphUrl: string; + first: number; + requiredPools?: string[]; + poolsQuery?: string; + poolsByIdQuery?: string; +}; + +@Injectable() +export class UniswapV2TheGraphPoolTokenAddressStrategy { + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} + + @Cache({ + instance: 'business', + key: (network: Network) => `studio-uniswap-v2-the-graph-pool-token-addresses:${network}:uniswap-v2`, + ttl: 5 * 60, + }) + async getPoolAddresses( + subgraphUrl: string, + first: number, + requiredPools: string[], + poolsQuery: string, + poolsByIdQuery: string, + ) { + const graphHelper = this.appToolkit.helpers.theGraphHelper; + + const [poolsData, poolsByIdData] = await Promise.all([ + graphHelper.request({ endpoint: subgraphUrl, query: poolsQuery, variables: { first } }), + graphHelper.request({ + endpoint: subgraphUrl, + query: poolsByIdQuery, + variables: { ids: requiredPools }, + }), + ]); + + const pools = poolsData.pairs ?? []; + const poolsById = poolsByIdData.pairs ?? []; + const poolIds = [...pools, ...poolsById].map(v => v.id.toLowerCase()); + const uniquepoolIds = uniq(poolIds); + return uniquepoolIds; + } + + build({ + subgraphUrl, + first, + requiredPools = [], + poolsQuery = DEFAULT_POOLS_QUERY, + poolsByIdQuery = DEFAULT_POOLS_BY_ID_QUERY, + }: UniswapV2TheGraphPoolTokenAddressStrategyParams): UniswapV2PoolTokenHelperParams< + T, + V + >['resolvePoolTokenAddresses'] { + return async () => { + const poolAddresses = this.getPoolAddresses(subgraphUrl, first, requiredPools, poolsQuery, poolsByIdQuery); + + return poolAddresses; + }; + } +} diff --git a/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-balance-helper.ts b/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-balance-helper.ts new file mode 100644 index 000000000..ec0327e03 --- /dev/null +++ b/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-token-balance-helper.ts @@ -0,0 +1,212 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { gql } from 'graphql-request'; +import { keyBy } from 'lodash'; + +import { drillBalance } from '~app-toolkit'; +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { + buildDollarDisplayItem, + buildPercentageDisplayItem, +} from '~app-toolkit/helpers/presentation/display-item.present'; +import { getTokenImg } from '~app-toolkit/helpers/presentation/image.present'; +import { ContractType } from '~position/contract.interface'; +import { AppTokenPosition } from '~position/position.interface'; +import { BaseToken } from '~position/token.interface'; +import { Network } from '~types/network.interface'; + +import { UniswapV2ContractFactory } from '../contracts'; + +import { UniswapV2PoolTokenDataProps } from './uniswap-v2.pool.token-helper'; + +type UniswapV2GetBalancesParams = { + address: string; + network: Network; + appId: string; + groupId: string; + subgraphUrl: string; + symbolPrefix: string; + fee?: number; + overrideDecimalsForTokens?: string[]; +}; + +type BalanceData = { + user?: { + liquidityPositions: { + liquidityTokenBalance: string; + pair: { + id: string; + reserve0: string; + reserve1: string; + reserveUSD: string; + token0: { + id: string; + decimals: string; + symbol: string; + }; + token1: { + id: string; + decimals: string; + symbol: string; + }; + totalSupply: string; + }; + }[]; + }; +}; + +const GET_BALANCE_QUERY = gql` + query getBalances($address: String!) { + user(id: $address) { + liquidityPositions { + liquidityTokenBalance + pair { + id + totalSupply + reserveUSD + reserve0 + reserve1 + token0 { + id + decimals + symbol + } + token1 { + id + decimals + symbol + } + } + } + } + } +`; + +const rename = (token: string) => { + const renameTo: Record = { + 'yDAI+yUSDC+yUSDT+yTUSD': 'Y Curve', + 'yyDAI+yUSDC+yUSDT+yTUSD': 'yUSD', + REPv2: 'REP', + '': 'AAVE', + }; + if (renameTo[token]) return renameTo[token]; + else return token; +}; + +@Injectable() +export class UniswapV2TheGraphPoolTokenBalanceHelper { + constructor( + @Inject(UniswapV2ContractFactory) private readonly contractFactory: UniswapV2ContractFactory, + @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, + ) {} + + async getBalances({ + address, + network, + appId, + groupId, + subgraphUrl, + symbolPrefix, + fee = 0.003, + overrideDecimalsForTokens = [], + }: UniswapV2GetBalancesParams) { + const multicall = this.appToolkit.getMulticall(network); + const prices = await this.appToolkit.getBaseTokenPrices(network); + const pricesByAddress = keyBy(prices, p => p.address); + const graphHelper = this.appToolkit.helpers.theGraphHelper; + const data = await graphHelper.requestGraph({ + endpoint: subgraphUrl, + query: GET_BALANCE_QUERY, + variables: { address: address.toLowerCase() }, + }); + + const balances = await Promise.all( + (data.user?.liquidityPositions ?? []).map(async lp => { + const pairAddress = lp.pair.id; + const contract = this.contractFactory.erc20({ address: pairAddress, network }); + const balanceRaw = await multicall.wrap(contract).balanceOf(address); + + // Resolve the tokens either from our base tokens or from data from TheGraph + const tokens: BaseToken[] = [lp.pair.token0, lp.pair.token1].map(tokenData => { + if (pricesByAddress[tokenData.id]) return pricesByAddress[tokenData.id]; + const overrideDecimals = overrideDecimalsForTokens.includes(tokenData.id); + const decimals = overrideDecimals ? 18 : +tokenData.decimals; + const symbol = rename(tokenData.symbol === 'WETH' ? 'ETH' : tokenData.symbol); + return { type: ContractType.BASE_TOKEN, address, network, symbol, decimals, price: 0 }; + }); + + // Resolve the reserves. In some cases, these need to be denormalized from TheGraph data + const reserves = [Number(lp.pair.reserve0), Number(lp.pair.reserve1)].map((reserve, i) => { + if (!overrideDecimalsForTokens.includes(tokens[i].address)) return reserve; + return reserve / 10 ** tokens[i].decimals; + }); + + // Resolve token spot prices + if (tokens[0].price && !tokens[1].price) { + // If we have the price of one of the tokens, we can infer the price of the other via the reserves + tokens[1].price = (tokens[0].price * reserves[0]) / reserves[1]; + } else if (!tokens[0].price && tokens[1].price) { + // Same can be said for the opposite token + tokens[0].price = (tokens[1].price * reserves[1]) / reserves[0]; + } else if (!tokens[0].price && !tokens[1].price) { + // In all other cases, rely on the price of the tokens as they stand in the pool. + const liquidity = Number(lp.pair.reserveUSD); + tokens[0].price = liquidity / reserves[0] / 2; + tokens[1].price = liquidity / reserves[1] / 2; + } + + const decimals = 18; + const liquidity = tokens[0].price * reserves[0] + tokens[1].price * reserves[1]; + const reservePercentages = tokens.map((t, i) => reserves[i] * (t.price / liquidity)); + const supply = Number(lp.pair.totalSupply); + const price = liquidity / supply; + const pricePerShare = [reserves[0] / supply, reserves[1] / supply]; + const volume = 0; + const volumeChangePercentage = 0; + const isBlocked = false; + + // Display Props + const label = `${symbolPrefix} ${tokens[0].symbol} / ${tokens[1].symbol}`; + const secondaryLabel = reservePercentages.map(p => `${Math.round(p * 100)}%`).join(' / '); + const images = [getTokenImg(tokens[0].address, network), getTokenImg(tokens[1].address, network)]; + const statsItems = [ + { label: 'Liquidity', value: buildDollarDisplayItem(liquidity) }, + { label: 'Volume', value: buildDollarDisplayItem(volume) }, + { label: 'Fee', value: buildPercentageDisplayItem(fee) }, + ]; + + const poolToken: AppTokenPosition = { + type: ContractType.APP_TOKEN, + address: pairAddress, + symbol: symbolPrefix, + decimals, + supply, + network, + appId, + groupId, + pricePerShare, + price, + tokens, + + dataProps: { + liquidity, + fee, + volume, + volumeChangePercentage, + isBlocked, + }, + + displayProps: { + label, + secondaryLabel, + images, + statsItems, + }, + }; + + return drillBalance(poolToken, balanceRaw.toString()); + }), + ); + + return balances; + } +} diff --git a/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-volume-strategy.ts b/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-volume-strategy.ts new file mode 100644 index 000000000..c403cd419 --- /dev/null +++ b/src/apps/uniswap-v2/helpers/uniswap-v2.the-graph.pool-volume-strategy.ts @@ -0,0 +1,214 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { gql } from 'graphql-request'; +import { uniqBy } from 'lodash'; + +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { BLOCKS_PER_DAY } from '~app-toolkit/constants/blocks'; + +import { UniswapFactory, UniswapPair } from '../contracts'; + +import { UniswapV2PoolTokenHelperParams } from './uniswap-v2.pool.token-helper'; + +type LastBlockSyncedResponse = { + _meta: { + block: { + number: number; + }; + }; +}; + +type PoolVolumesResponse = { + pairs: { + id: string; + volumeUSD: string; + untrackedVolumeUSD: string; + }[]; +}; + +type SinglePoolVolumeResponse = { + pair: { + id: string; + volumeUSD: string; + untrackedVolumeUSD: string; + }; +}; + +const DEFAULT_LAST_BLOCK_SYNCED_ON_GRAPH_QUERY = gql` + { + _meta { + block { + number + } + } + } +`; + +// @TODO where: { reserveUSD_gte: $minLiquidity } +const DEFAULT_POOL_VOLUMES_QUERY = gql` + query getCurrentPairVolumes($first: Int) { + pairs(first: $first, skip: 0, orderBy: reserveUSD, orderDirection: desc) { + id + volumeUSD + untrackedVolumeUSD + } + } +`; + +const DEFAULT_POOL_VOLUMES_AT_BLOCK_QUERY = gql` + query getCurrentPairVolumes($first: Int, $block: Int) { + pairs(first: $first, skip: 0, orderBy: reserveUSD, orderDirection: desc, block: { number: $block }) { + id + volumeUSD + untrackedVolumeUSD + } + } +`; + +const DEFAULT_POOL_VOLUMES_BY_ID_QUERY = gql` + query getPastPairVolumesByID($ids: [String]) { + pairs(where: { id_in: $ids }) { + id + volumeUSD + untrackedVolumeUSD + } + } +`; + +const DEFAULT_POOL_VOLUMES_BY_ID_AT_BLOCK_QUERY = gql` + query getPastPairVolumesByID($ids: [String], $block: Int) { + pairs(where: { id_in: $ids }, block: { number: $block }) { + id + volumeUSD + untrackedVolumeUSD + } + } +`; + +const DEFAULT_SINGLE_POOL_VOLUME_AT_BLOCK_QUERY = gql` + query getSinglePairVolume($id: String, $block: Int) { + pair(id: $id, block: { number: $block }) { + id + volumeUSD + untrackedVolumeUSD + } + } +`; + +type UniswapV2TheGraphPoolVolumeStrategyParams = { + subgraphUrl: string; + first: number; + blocksPerDay?: number; + requiredPools?: string[]; + lastBlockSyncedOnGraphQuery?: string; + poolVolumesQuery?: string; + poolVolumesAtBlockQuery?: string; + poolVolumesByIdQuery?: string; + poolVolumesByIdAtBlockQuery?: string; + singlePoolVolumeAtBlockQuery?: string; +}; + +@Injectable() +export class UniswapV2TheGraphPoolVolumeStrategy { + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} + + build({ + subgraphUrl, + first, + blocksPerDay = 0, + requiredPools = [], + lastBlockSyncedOnGraphQuery = DEFAULT_LAST_BLOCK_SYNCED_ON_GRAPH_QUERY, + poolVolumesQuery = DEFAULT_POOL_VOLUMES_QUERY, + poolVolumesAtBlockQuery = DEFAULT_POOL_VOLUMES_AT_BLOCK_QUERY, + poolVolumesByIdQuery = DEFAULT_POOL_VOLUMES_BY_ID_QUERY, + poolVolumesByIdAtBlockQuery = DEFAULT_POOL_VOLUMES_BY_ID_AT_BLOCK_QUERY, + singlePoolVolumeAtBlockQuery = DEFAULT_SINGLE_POOL_VOLUME_AT_BLOCK_QUERY, + }: UniswapV2TheGraphPoolVolumeStrategyParams): UniswapV2PoolTokenHelperParams['resolvePoolVolumes'] { + return async ({ network }) => { + // Get block from 1 day ago + const provider = this.appToolkit.getNetworkProvider(network); + const block = await provider.getBlockNumber(); + const block1DayAgo = block - (blocksPerDay ?? BLOCKS_PER_DAY[network]); + + const graphHelper = this.appToolkit.helpers.theGraphHelper; + + // Get last block synced on graph; if the graph is not caught up to yesterday, exit early + const graphMetaData = await graphHelper.request({ + endpoint: subgraphUrl, + query: lastBlockSyncedOnGraphQuery, + }); + + const blockNumberLastSynced = graphMetaData._meta.block.number; + if (block1DayAgo > blockNumberLastSynced) return []; + + // Retrieve volume data from TheGraph (@TODO Cache this) + const [volumeData, volumeData1DayAgo, volumeByIDData, volumeByIDData1DayAgo] = await Promise.all([ + graphHelper.request({ + endpoint: subgraphUrl, + query: poolVolumesQuery, + variables: { first }, + }), + graphHelper.request({ + endpoint: subgraphUrl, + query: poolVolumesAtBlockQuery, + variables: { first, block: block1DayAgo }, + }), + graphHelper.request({ + endpoint: subgraphUrl, + query: poolVolumesByIdQuery, + variables: { ids: requiredPools }, + }), + graphHelper.request({ + endpoint: subgraphUrl, + query: poolVolumesByIdAtBlockQuery, + variables: { ids: requiredPools, block: block1DayAgo }, + }), + ]); + + // Merge all volume data for today and merge all volume data for yesterday + const allVolumeData = uniqBy([...volumeData.pairs, ...volumeByIDData.pairs], p => p.id); + const allVolumeData1DayAgo = uniqBy([...volumeData1DayAgo.pairs, ...volumeByIDData1DayAgo.pairs], p => p.id); + + const poolVolumes = await Promise.all( + allVolumeData.map(async pairVolumeToday => { + // Find the matching volume entry from yesterday + let poolVolumeYesterday = allVolumeData1DayAgo.find(p => p.id === pairVolumeToday.id); + + // Some pairs move in and out of the top requested pairs, so retrieve the missing pair-volumes by ID individually + if (!poolVolumeYesterday) { + poolVolumeYesterday = await graphHelper + .request({ + endpoint: subgraphUrl, + query: singlePoolVolumeAtBlockQuery, + variables: { + id: pairVolumeToday.id, + block, + }, + }) + .then(v => v.pair); + } + + // Calculate volume chnage between yesterday and today wherever applicable + let volumeChangeUSD: number, volumeChangePercentage: number; + if (pairVolumeToday?.volumeUSD && poolVolumeYesterday?.volumeUSD) { + const volumeUSDToday = Number(pairVolumeToday.volumeUSD); + const volumeUSDYesterday = Number(poolVolumeYesterday.volumeUSD); + volumeChangeUSD = volumeUSDToday - volumeUSDYesterday; + volumeChangePercentage = Math.abs(volumeChangeUSD / volumeUSDYesterday); + } else if (pairVolumeToday?.untrackedVolumeUSD && poolVolumeYesterday?.untrackedVolumeUSD) { + const volumeUSDToday = Number(pairVolumeToday.untrackedVolumeUSD); + const volumeUSDYesterday = Number(poolVolumeYesterday.untrackedVolumeUSD); + volumeChangeUSD = volumeUSDToday - volumeUSDYesterday; + volumeChangePercentage = Math.abs(volumeChangeUSD / volumeUSDYesterday); + } else { + volumeChangeUSD = 0; + volumeChangePercentage = 0; + } + + return { poolAddress: pairVolumeToday.id, volumeChangeUSD, volumeChangePercentage }; + }), + ); + + return poolVolumes; + }; + } +} diff --git a/src/apps/uniswap-v2/uniswap-v2.definition.ts b/src/apps/uniswap-v2/uniswap-v2.definition.ts new file mode 100644 index 000000000..7b3c5b88e --- /dev/null +++ b/src/apps/uniswap-v2/uniswap-v2.definition.ts @@ -0,0 +1,32 @@ +import { Register } from '~app-toolkit/decorators'; +import { AppDefinition } from '~app/app.definition'; +import { GroupType, ProtocolAction, ProtocolTag } from '~app/app.interface'; +import { Network } from '~types/network.interface'; + +export const UNISWAP_V2_DEFINITION = { + id: 'uniswap-v2', + name: 'Uniswap V2', + description: `A protocol for trading and automated liquidity provision on Ethereum.`, + groups: { + pool: { id: 'pool', type: GroupType.TOKEN }, + }, + url: 'https://uniswap.org/', + tags: [ProtocolTag.LIQUIDITY_POOL], + supportedNetworks: { + [Network.ETHEREUM_MAINNET]: [ProtocolAction.VIEW], + }, + primaryColor: '#f80076', + token: { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + network: Network.ETHEREUM_MAINNET, + }, +}; + +@Register.AppDefinition(UNISWAP_V2_DEFINITION.id) +export class UniswapV2AppDefinition extends AppDefinition { + constructor() { + super(UNISWAP_V2_DEFINITION); + } +} + +export default UNISWAP_V2_DEFINITION; diff --git a/src/apps/uniswap-v2/uniswap-v2.module.ts b/src/apps/uniswap-v2/uniswap-v2.module.ts new file mode 100644 index 000000000..16938ed47 --- /dev/null +++ b/src/apps/uniswap-v2/uniswap-v2.module.ts @@ -0,0 +1,37 @@ +import { Module } from '@nestjs/common'; + +import { AbstractDynamicApp } from '~app/app.dynamic-module'; + +import { UniswapV2ContractFactory } from './contracts'; +import { EthereumUniswapV2BalanceFetcher } from './ethereum/uniswap-v2.balance-fetcher'; +import { EthereumUniswapV2PoolTokenFetcher } from './ethereum/uniswap-v2.pool.token-fetcher'; +import { UniswapV2OnChainPoolTokenAddressStrategy } from './helpers/uniswap-v2.on-chain.pool-token-address-strategy'; +import { UniswapV2PoolTokenHelper } from './helpers/uniswap-v2.pool.token-helper'; +import { UniswapV2TheGraphPoolTokenAddressStrategy } from './helpers/uniswap-v2.the-graph.pool-token-address-strategy'; +import { UniswapV2TheGraphPoolTokenBalanceHelper } from './helpers/uniswap-v2.the-graph.pool-token-balance-helper'; +import { UniswapV2TheGraphPoolVolumeStrategy } from './helpers/uniswap-v2.the-graph.pool-volume-strategy'; +import { UniswapV2AppDefinition } from './uniswap-v2.definition'; + +@Module({ + providers: [ + UniswapV2AppDefinition, + UniswapV2ContractFactory, + EthereumUniswapV2PoolTokenFetcher, + EthereumUniswapV2BalanceFetcher, + // Helpers + UniswapV2PoolTokenHelper, + UniswapV2OnChainPoolTokenAddressStrategy, + UniswapV2TheGraphPoolTokenAddressStrategy, + UniswapV2TheGraphPoolVolumeStrategy, + UniswapV2TheGraphPoolTokenBalanceHelper, + ], + exports: [ + UniswapV2ContractFactory, + UniswapV2PoolTokenHelper, + UniswapV2OnChainPoolTokenAddressStrategy, + UniswapV2TheGraphPoolTokenAddressStrategy, + UniswapV2TheGraphPoolVolumeStrategy, + UniswapV2TheGraphPoolTokenBalanceHelper, + ], +}) +export class UniswapV2AppModule extends AbstractDynamicApp() {}