From 3087718d222998756d67ebf152b61f37ec67f038 Mon Sep 17 00:00:00 2001 From: Marlon Wiprud Date: Wed, 15 Jun 2022 06:35:07 -0400 Subject: [PATCH 1/3] chore: install new deps --- package-lock.json | 35 +++++++++++++++++++++++++++++++++-- package.json | 7 +++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78e67ab..c10fca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "simple-uniswap-sdk", - "version": "3.6.0", + "version": "3.6.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "simple-uniswap-sdk", - "version": "3.6.0", + "version": "3.6.2", "license": "ISC", "dependencies": { + "big.js": "^6.2.0", "bignumber.js": "^9.0.1", "ethereum-abi-types-generator": "^1.1.6", "ethereum-multicall": "^2.7.0", @@ -17,6 +18,7 @@ "rxjs": "^6.6.3" }, "devDependencies": { + "@types/big.js": "^6.1.3", "@types/jest": "^26.0.20", "@types/node-fetch": "^2.5.12", "jest": "^24.9.0", @@ -1279,6 +1281,12 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/big.js": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.1.3.tgz", + "integrity": "sha512-fHh2h1cFlvGP0kFCqoAsnuQoM0n3xHB6HxgZvELt7dji+BtK/j938MRL0nG5AA45EgibuFcPjgLlkqfUPCyoKw==", + "dev": true + }, "node_modules/@types/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", @@ -1931,6 +1939,18 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, + "node_modules/big.js": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.0.tgz", + "integrity": "sha512-paIKvJiAaOYdLt6MfnvxkDo64lTOV257XYJyX3oJnJQocIclUn+48k6ZerH/c5FxWE6DGJu1TKDYis7tqHg9kg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, "node_modules/bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", @@ -8864,6 +8884,12 @@ "@babel/types": "^7.3.0" } }, + "@types/big.js": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.1.3.tgz", + "integrity": "sha512-fHh2h1cFlvGP0kFCqoAsnuQoM0n3xHB6HxgZvELt7dji+BtK/j938MRL0nG5AA45EgibuFcPjgLlkqfUPCyoKw==", + "dev": true + }, "@types/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", @@ -9387,6 +9413,11 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, + "big.js": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.0.tgz", + "integrity": "sha512-paIKvJiAaOYdLt6MfnvxkDo64lTOV257XYJyX3oJnJQocIclUn+48k6ZerH/c5FxWE6DGJu1TKDYis7tqHg9kg==" + }, "bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", diff --git a/package.json b/package.json index f6e587a..6beb6ba 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "gen-uniswap-factory-v2-abi": "abi-types-generator ./src/ABI/uniswap-factory-v2.json --output=./src/ABI/types --name=uniswap-factory-v2 --provider=ethers_v5", "gen-uniswap-factory-v3-abi": "abi-types-generator ./src/ABI/uniswap-factory-v3.json --output=./src/ABI/types --name=uniswap-factory-v3 --provider=ethers_v5", "gen-uniswap-pair-v2-abi": "abi-types-generator ./src/ABI/uniswap-pair-v2.json --output=./src/ABI/types --name=uniswap-pair-v2 --provider=ethers_v5", + "gen-uniswap-pair-v2-real-abi": "abi-types-generator ./src/ABI/uniswap-pair-v2-real.json --output=./src/ABI/types --name=uniswap-pair-v2-real --provider=ethers_v5", "gen-uniswap-quoter-v3-abi": "abi-types-generator ./src/ABI/uniswap-quoter-v3.json --output=./src/ABI/types --name=uniswap-quoter-v3 --provider=ethers_v5", "gen-all-contract-types": "npm run gen-erc20-abi && npm run gen-uniswap-router-v2-abi && npm run gen-uniswap-factory-v2-abi && npm run gen-uniswap-pair-v2-abi && npm run gen-uniswap-factory-v3-abi && npm run gen-uniswap-router-v3-abi && npm run gen-uniswap-quoter-v3-abi", "prepublishOnly": "npm run build" @@ -38,14 +39,16 @@ }, "homepage": "https://github.com/uniswap-integration/simple-uniswap-sdk#readme", "dependencies": { + "big.js": "^6.2.0", "bignumber.js": "^9.0.1", + "ethereum-abi-types-generator": "^1.1.6", "ethereum-multicall": "^2.7.0", "ethers": "^5.0.26", "node-fetch": "^2.6.1", - "rxjs": "^6.6.3", - "ethereum-abi-types-generator": "^1.1.6" + "rxjs": "^6.6.3" }, "devDependencies": { + "@types/big.js": "^6.1.3", "@types/jest": "^26.0.20", "@types/node-fetch": "^2.5.12", "jest": "^24.9.0", From 2bef5a3876b56b50f41d21d4b36105faa42d32b8 Mon Sep 17 00:00:00 2001 From: Marlon Wiprud Date: Wed, 15 Jun 2022 06:38:29 -0400 Subject: [PATCH 2/3] chore: wrapped interactions with uniswap pair contract --- .gitignore | 2 + src/ABI/types/uniswap-pair-v2-real.ts | 385 ++++++++++ src/ABI/uniswap-pair-v2-real.json | 713 ++++++++++++++++++ src/factories/pair/uniswap-pair.factory.ts | 133 +++- .../pair/v2/uniswap-pair-contract.v2.ts | 49 ++ .../uniswap-contract-context-v2.ts | 17 +- 6 files changed, 1263 insertions(+), 36 deletions(-) create mode 100755 src/ABI/types/uniswap-pair-v2-real.ts create mode 100644 src/ABI/uniswap-pair-v2-real.json create mode 100644 src/factories/pair/v2/uniswap-pair-contract.v2.ts diff --git a/.gitignore b/.gitignore index 6704566..050779a 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ dist # TernJS port file .tern-port + +.vscode \ No newline at end of file diff --git a/src/ABI/types/uniswap-pair-v2-real.ts b/src/ABI/types/uniswap-pair-v2-real.ts new file mode 100755 index 0000000..33b6a14 --- /dev/null +++ b/src/ABI/types/uniswap-pair-v2-real.ts @@ -0,0 +1,385 @@ +import { + ContractTransaction, + ContractInterface, + BytesLike as Arrayish, + BigNumber, + BigNumberish, +} from 'ethers'; +import { EthersContractContextV5 } from 'ethereum-abi-types-generator'; + +export type ContractContext = EthersContractContextV5< + UniswapPairV2Real, + UniswapPairV2RealMethodNames, + UniswapPairV2RealEventsContext, + UniswapPairV2RealEvents +>; + +export declare type EventFilter = { + address?: string; + topics?: Array; + fromBlock?: string | number; + toBlock?: string | number; +}; + +export interface ContractTransactionOverrides { + /** + * The maximum units of gas for the transaction to use + */ + gasLimit?: number; + /** + * The price (in wei) per unit of gas + */ + gasPrice?: BigNumber | string | number | Promise; + /** + * The nonce to use in the transaction + */ + nonce?: number; + /** + * The amount to send with the transaction (i.e. msg.value) + */ + value?: BigNumber | string | number | Promise; + /** + * The chain ID (or network ID) to use + */ + chainId?: number; +} + +export interface ContractCallOverrides { + /** + * The address to execute the call as + */ + from?: string; + /** + * The maximum units of gas for the transaction to use + */ + gasLimit?: number; +} +export type UniswapPairV2RealEvents = + | 'Approval' + | 'Burn' + | 'Mint' + | 'Swap' + | 'Sync' + | 'Transfer'; +export interface UniswapPairV2RealEventsContext { + Approval(...parameters: any): EventFilter; + Burn(...parameters: any): EventFilter; + Mint(...parameters: any): EventFilter; + Swap(...parameters: any): EventFilter; + Sync(...parameters: any): EventFilter; + Transfer(...parameters: any): EventFilter; +} +export type UniswapPairV2RealMethodNames = + | 'new' + | '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'; +export interface GetReservesResponse { + _reserve0: BigNumber; + 0: BigNumber; + _reserve1: BigNumber; + 1: BigNumber; + _blockTimestampLast: number; + 2: number; + length: 3; +} +export interface UniswapPairV2Real { + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: constructor + */ + 'new'(overrides?: ContractTransactionOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + DOMAIN_SEPARATOR(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + MINIMUM_LIQUIDITY(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + PERMIT_TYPEHASH(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + * @param parameter0 Type: address, Indexed: false + * @param parameter1 Type: address, Indexed: false + */ + allowance( + parameter0: string, + parameter1: string, + overrides?: ContractCallOverrides + ): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param spender Type: address, Indexed: false + * @param value Type: uint256, Indexed: false + */ + approve( + spender: string, + value: BigNumberish, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + * @param parameter0 Type: address, Indexed: false + */ + balanceOf( + parameter0: string, + overrides?: ContractCallOverrides + ): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param to Type: address, Indexed: false + */ + burn( + to: string, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + decimals(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + factory(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + getReserves(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param _token0 Type: address, Indexed: false + * @param _token1 Type: address, Indexed: false + */ + initialize( + _token0: string, + _token1: string, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + kLast(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param to Type: address, Indexed: false + */ + mint( + to: string, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + name(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + * @param parameter0 Type: address, Indexed: false + */ + nonces( + parameter0: string, + overrides?: ContractCallOverrides + ): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param owner Type: address, Indexed: false + * @param spender Type: address, Indexed: false + * @param value Type: uint256, Indexed: false + * @param deadline Type: uint256, Indexed: false + * @param v Type: uint8, Indexed: false + * @param r Type: bytes32, Indexed: false + * @param s Type: bytes32, Indexed: false + */ + permit( + owner: string, + spender: string, + value: BigNumberish, + deadline: BigNumberish, + v: BigNumberish, + r: Arrayish, + s: Arrayish, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + price0CumulativeLast(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + price1CumulativeLast(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param to Type: address, Indexed: false + */ + skim( + to: string, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param amount0Out Type: uint256, Indexed: false + * @param amount1Out Type: uint256, Indexed: false + * @param to Type: address, Indexed: false + * @param data Type: bytes, Indexed: false + */ + swap( + amount0Out: BigNumberish, + amount1Out: BigNumberish, + to: string, + data: Arrayish, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + symbol(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + */ + sync(overrides?: ContractTransactionOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + token0(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + token1(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: true + * StateMutability: view + * Type: function + */ + totalSupply(overrides?: ContractCallOverrides): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param to Type: address, Indexed: false + * @param value Type: uint256, Indexed: false + */ + transfer( + to: string, + value: BigNumberish, + overrides?: ContractTransactionOverrides + ): Promise; + /** + * Payable: false + * Constant: false + * StateMutability: nonpayable + * Type: function + * @param from Type: address, Indexed: false + * @param to Type: address, Indexed: false + * @param value Type: uint256, Indexed: false + */ + transferFrom( + from: string, + to: string, + value: BigNumberish, + overrides?: ContractTransactionOverrides + ): Promise; +} diff --git a/src/ABI/uniswap-pair-v2-real.json b/src/ABI/uniswap-pair-v2-real.json new file mode 100644 index 0000000..53582c1 --- /dev/null +++ b/src/ABI/uniswap-pair-v2-real.json @@ -0,0 +1,713 @@ +[ + { + "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/factories/pair/uniswap-pair.factory.ts b/src/factories/pair/uniswap-pair.factory.ts index ab8f32e..361ecbc 100644 --- a/src/factories/pair/uniswap-pair.factory.ts +++ b/src/factories/pair/uniswap-pair.factory.ts @@ -1,30 +1,36 @@ -import BigNumber from 'bignumber.js'; -import { Subject } from 'rxjs'; -import { CoinGecko } from '../../coin-gecko'; -import { Constants } from '../../common/constants'; -import { ErrorCodes } from '../../common/errors/error-codes'; -import { UniswapError } from '../../common/errors/uniswap-error'; +import Big from "big.js"; +import BigNumber from "bignumber.js"; +import { Subject } from "rxjs"; +import { CoinGecko } from "../../coin-gecko"; +import { Constants } from "../../common/constants"; +import { ErrorCodes } from "../../common/errors/error-codes"; +import { UniswapError } from "../../common/errors/uniswap-error"; import { removeEthFromContractAddress, turnTokenIntoEthForResponse, -} from '../../common/tokens/eth'; -import { deepClone } from '../../common/utils/deep-clone'; -import { getTradePath } from '../../common/utils/trade-path'; -import { TradePath } from '../../enums/trade-path'; -import { UniswapVersion } from '../../enums/uniswap-version'; -import { uniswapContracts } from '../../uniswap-contract-context/get-uniswap-contracts'; -import { AllPossibleRoutes } from '../router/models/all-possible-routes'; -import { BestRouteQuotes } from '../router/models/best-route-quotes'; -import { RouteQuote } from '../router/models/route-quote'; -import { UniswapRouterFactory } from '../router/uniswap-router.factory'; -import { AllowanceAndBalanceOf } from '../token/models/allowance-balance-of'; -import { Token } from '../token/models/token'; -import { TokenFactory } from '../token/token.factory'; -import { CurrentTradeContext } from './models/current-trade-context'; -import { TradeContext } from './models/trade-context'; -import { TradeDirection } from './models/trade-direction'; -import { Transaction } from './models/transaction'; -import { UniswapPairFactoryContext } from './models/uniswap-pair-factory-context'; +} from "../../common/tokens/eth"; +import { deepClone } from "../../common/utils/deep-clone"; +import { getTradePath } from "../../common/utils/trade-path"; +import { TradePath } from "../../enums/trade-path"; +import { UniswapVersion } from "../../enums/uniswap-version"; +import { uniswapContracts } from "../../uniswap-contract-context/get-uniswap-contracts"; +import { AllPossibleRoutes } from "../router/models/all-possible-routes"; +import { BestRouteQuotes } from "../router/models/best-route-quotes"; +import { RouteQuote } from "../router/models/route-quote"; +import { UniswapRouterFactory } from "../router/uniswap-router.factory"; +import { AllowanceAndBalanceOf } from "../token/models/allowance-balance-of"; +import { Token } from "../token/models/token"; +import { TokenFactory } from "../token/token.factory"; +import { CurrentTradeContext } from "./models/current-trade-context"; +import { TradeContext } from "./models/trade-context"; +import { TradeDirection } from "./models/trade-direction"; +import { Transaction } from "./models/transaction"; +import { UniswapPairFactoryContext } from "./models/uniswap-pair-factory-context"; +import { UniswapPairContractFactoryPublicV2 } from "./v2/uniswap-pair-contract.factory.public.v2"; +import { + PairReserves, + UniswapPairContractV2, +} from "./v2/uniswap-pair-contract.v2"; export class UniswapPairFactory { private _fromTokenFactory = new TokenFactory( @@ -234,7 +240,7 @@ export class UniswapPairFactory { */ public async allowance(uniswapVersion: UniswapVersion): Promise { if (this.tradePath() === TradePath.ethToErc20) { - return '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + return "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; } const allowance = await this._fromTokenFactory.allowance( @@ -255,7 +261,7 @@ export class UniswapPairFactory { ): Promise { if (this.tradePath() === TradePath.ethToErc20) { throw new UniswapError( - 'You do not need to generate approve uniswap allowance when doing eth > erc20', + "You do not need to generate approve uniswap allowance when doing eth > erc20", ErrorCodes.generateApproveMaxAllowanceDataNotAllowed ); } @@ -268,7 +274,7 @@ export class UniswapPairFactory { : uniswapContracts.v3.getRouterAddress( this._uniswapPairFactoryContext.settings.cloneUniswapContractDetails ), - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ); return { @@ -304,6 +310,68 @@ export class UniswapPairFactory { }); } + private async getPairReserves( + fromToken: string, + toToken: string + ): Promise { + const network = this._uniswapPairFactoryContext.ethersProvider.network(); + const pairFactory = new UniswapPairContractFactoryPublicV2({ + chainId: network.chainId, + }); + + const pairAddress = await pairFactory.getPair(fromToken, toToken); + + const pairContract = new UniswapPairContractV2( + this._uniswapPairFactoryContext.ethersProvider, + pairAddress + ); + + return pairContract.getReserves(); + } + + private calculateTrade( + poolA: BigNumber, + poolB: BigNumber, + tradeInput: BigNumber + ) { + const _poolA = new Big(poolA.toString()); + const _poolB = new Big(poolB.toString()); + const input = new Big(tradeInput.toString()); + + // constant prduct is the starting value of the A side of the pool + const CP = new Big(_poolA.toString()); + + const marketPrice = _poolA.div(_poolB); + const newPoolA = _poolA.add(input); + const newPoolB = CP.div(newPoolA).mul(_poolB); + const recieve = _poolB.sub(newPoolB); + const newMarketPrice = input.div(recieve); + + const priceDifference = newMarketPrice.sub(marketPrice); + + const priceImpact = priceDifference.div(marketPrice).mul(100); + + return { + oldMarketPrice: marketPrice, + newMarketPrice: newMarketPrice, + recieve: recieve, + priceImpact: priceImpact, + }; + } + + public async newTradeCalculator(fromToken: string, toToken: string) { + const reserves = await this.getPairReserves(fromToken, toToken); + return (from: string, to: string, tradeInput: BigNumber) => { + // you can change the order of the token addresses + // but cannot use different tokens + if (!reserves[from] || !reserves[to]) { + throw new Error("unknown token address"); + } + + this.calculateTrade(reserves[from], reserves[to], tradeInput); + }; + } + /** * finds the best price and path for Erc20ToEth * @param baseConvertRequest The base convert request can be both input or output direction @@ -528,7 +596,7 @@ export class UniswapPairFactory { private watchTradePrice(): void { if (!this._watchingBlocks) { this._uniswapPairFactoryContext.ethersProvider.provider.on( - 'block', + "block", async () => { await this.handleNewBlock(); } @@ -542,7 +610,7 @@ export class UniswapPairFactory { */ private unwatchTradePrice(): void { this._uniswapPairFactoryContext.ethersProvider.provider.removeAllListeners( - 'block' + "block" ); this._watchingBlocks = false; } @@ -558,19 +626,26 @@ export class UniswapPairFactory { ); if ( + // validate from address matches trade.fromToken.contractAddress === this._currentTradeContext.fromToken.contractAddress && + // validate to address matches trade.toToken.contractAddress === this._currentTradeContext.toToken.contractAddress && + // validate sender address matches trade.transaction.from === this._uniswapPairFactoryContext.ethereumAddress ) { if ( + // validate that quote has changed trade.expectedConvertQuote !== this._currentTradeContext.expectedConvertQuote || + // validate that route has changed trade.routeText !== this._currentTradeContext.routeText || + // validate that fee has changed trade.liquidityProviderFee !== this._currentTradeContext.liquidityProviderFee || + // validate that trade has expired this._currentTradeContext.tradeExpires > this._uniswapRouterFactory.generateTradeDeadlineUnixTime() ) { diff --git a/src/factories/pair/v2/uniswap-pair-contract.v2.ts b/src/factories/pair/v2/uniswap-pair-contract.v2.ts new file mode 100644 index 0000000..233e391 --- /dev/null +++ b/src/factories/pair/v2/uniswap-pair-contract.v2.ts @@ -0,0 +1,49 @@ +import { BigNumber } from "bignumber.js"; +import { ContractContext as PairContractContext } from "../../../ABI/types/uniswap-pair-v2-real"; +import { EthersProvider } from "../../../ethers-provider"; +import { UniswapContractContextV2 } from "../../../uniswap-contract-context/uniswap-contract-context-v2"; + +export interface PairReserves { + tokenA: BigNumber; + tokenB: BigNumber; + timestamp: number; +} + +interface SwapListener { + ( + tokenAIn: BigNumber, + tokenBIn: BigNumber, + tokenAOut: BigNumber, + tokenBOut: BigNumber, + to: string + ): void; +} + +export class UniswapPairContractV2 { + private _uniswapPair = this._ethersProvider.getContract( + JSON.stringify(UniswapContractContextV2.pairAbiReal), + this._pairAddress + ); + + constructor( + private _ethersProvider: EthersProvider, + private _pairAddress: string = UniswapContractContextV2.pairAddress + ) {} + + public async getReserves(): Promise { + const resp = await this._uniswapPair.getReserves(); + return { + tokenA: new BigNumber(resp[0].toString()), + tokenB: new BigNumber(resp[1].toString()), + timestamp: resp[2], + }; + } + + public subsribeSwap(listener: SwapListener) { + this._uniswapPair.addListener("Swap", listener); + } + + public removeSwapListeners() { + this._uniswapPair.removeAllListeners("Swap"); + } +} diff --git a/src/uniswap-contract-context/uniswap-contract-context-v2.ts b/src/uniswap-contract-context/uniswap-contract-context-v2.ts index 43c2d9d..2d7774e 100644 --- a/src/uniswap-contract-context/uniswap-contract-context-v2.ts +++ b/src/uniswap-contract-context/uniswap-contract-context-v2.ts @@ -1,33 +1,36 @@ -import { JsonFragment } from '@ethersproject/abi'; +import { JsonFragment } from "@ethersproject/abi"; export class UniswapContractContextV2 { /** * The uniswap router address */ - public static routerAddress = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; + public static routerAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"; /** * The uniswap factory address */ - public static factoryAddress = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'; + public static factoryAddress = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"; /** * The uniswap pair address */ - public static pairAddress = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'; + public static pairAddress = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"; /** * Uniswap v2 router */ - public static routerAbi: JsonFragment[] = require('../ABI/uniswap-router-v2.json'); + public static routerAbi: JsonFragment[] = require("../ABI/uniswap-router-v2.json"); /** * Uniswap v2 factory */ - public static factoryAbi: JsonFragment[] = require('../ABI/uniswap-factory-v2.json'); + public static factoryAbi: JsonFragment[] = require("../ABI/uniswap-factory-v2.json"); /** * Uniswap v2 pair */ - public static pairAbi: JsonFragment[] = require('../ABI/uniswap-pair-v2.json'); + public static pairAbi: JsonFragment[] = require("../ABI/uniswap-pair-v2.json"); + + /**Uniswap v2 real pair */ + public static pairAbiReal: JsonFragment[] = require("../ABI/uniswap-pair-v2-real.json"); } From 384b23fe0b9a27e684ff11ce55722524594b8a14 Mon Sep 17 00:00:00 2001 From: Marlon Wiprud Date: Fri, 17 Jun 2022 09:07:32 -0400 Subject: [PATCH 3/3] feat: pair calculator class to manage pool subscription --- src/ethers-provider.ts | 20 +- src/factories/pair/models/trade-calculator.ts | 14 + src/factories/pair/playground.spec.ts | 138 ++++++++ src/factories/pair/uniswap-pair.calculator.ts | 112 +++++++ .../pair/uniswap-pair.factory.spec.ts | 300 ++++++++++-------- src/factories/pair/uniswap-pair.factory.ts | 72 +---- .../pair/v2/uniswap-pair-contract.v2.ts | 16 +- 7 files changed, 462 insertions(+), 210 deletions(-) create mode 100644 src/factories/pair/models/trade-calculator.ts create mode 100644 src/factories/pair/playground.spec.ts create mode 100644 src/factories/pair/uniswap-pair.calculator.ts diff --git a/src/ethers-provider.ts b/src/ethers-provider.ts index e952331..9ddd150 100644 --- a/src/ethers-provider.ts +++ b/src/ethers-provider.ts @@ -1,9 +1,9 @@ -import BigNumber from 'bignumber.js'; -import { Contract, ContractInterface, providers } from 'ethers'; -import { ErrorCodes } from './common/errors/error-codes'; -import { UniswapError } from './common/errors/uniswap-error'; -import { ChainId, ChainNames } from './enums/chain-id'; -import { CustomNetwork } from './factories/pair/models/custom-network'; +import BigNumber from "bignumber.js"; +import { Contract, ContractInterface, providers } from "ethers"; +import { ErrorCodes } from "./common/errors/error-codes"; +import { UniswapError } from "./common/errors/uniswap-error"; +import { ChainId, ChainNames } from "./enums/chain-id"; +import { CustomNetwork } from "./factories/pair/models/custom-network"; export interface ChainIdAndProvider { chainId: ChainId; @@ -22,7 +22,7 @@ export class EthersProvider { | providers.JsonRpcProvider | providers.InfuraProvider | providers.Web3Provider; - constructor(private _providerContext: ChainIdAndProvider | EthereumProvider) { + constructor(public _providerContext: ChainIdAndProvider | EthereumProvider) { const chainId = (this._providerContext).chainId; if (chainId) { const chainName = this.getChainName(chainId); @@ -47,7 +47,7 @@ export class EthersProvider { .ethereumProvider; if (!ethereumProvider) { throw new UniswapError( - 'Wrong ethers provider context', + "Wrong ethers provider context", ErrorCodes.wrongEthersProviderContext ); } @@ -120,7 +120,7 @@ export class EthersProvider { } throw new UniswapError( - 'chainId can not be found on the provider', + "chainId can not be found on the provider", ErrorCodes.chainIdCanNotBeFound ); } @@ -179,6 +179,6 @@ export class EthersProvider { * Get the api key */ private get _getApiKey(): string { - return '9aa3d95b3bc440fa88ea12eaa4456161'; + return "9aa3d95b3bc440fa88ea12eaa4456161"; } } diff --git a/src/factories/pair/models/trade-calculator.ts b/src/factories/pair/models/trade-calculator.ts new file mode 100644 index 0000000..b8fcb22 --- /dev/null +++ b/src/factories/pair/models/trade-calculator.ts @@ -0,0 +1,14 @@ +import { BigNumber } from "bignumber.js"; +import Big from "big.js"; + +export interface Quote { + oldMarketPrice: Big; + newMarketPrice: Big; + recieve: Big; + priceImpact: Big; +} + +export interface TradeCalculator { + quote: (input: BigNumber) => Quote; + reverseQuote: (input: BigNumber) => Quote; +} diff --git a/src/factories/pair/playground.spec.ts b/src/factories/pair/playground.spec.ts new file mode 100644 index 0000000..ac59e2e --- /dev/null +++ b/src/factories/pair/playground.spec.ts @@ -0,0 +1,138 @@ +import { ETH } from "../../common/tokens"; +import { ChainId } from "../../enums/chain-id"; +import { UniswapVersion } from "../../enums/uniswap-version"; +import { MockEthereumAddress } from "../../mocks/ethereum-address.mock"; +import { MOCKFUN } from "../../mocks/fun-token.mock"; +import { UniswapPairContextForChainId } from "./models/uniswap-pair-contexts"; +import { UniswapPairSettings } from "./models/uniswap-pair-settings"; +import { UniswapPair } from "./uniswap-pair"; +import { UniswapPairContractFactoryPublicV2 } from "./v2/uniswap-pair-contract.factory.public.v2"; +import { UniswapPairContractV2 } from "./v2/uniswap-pair-contract.v2"; +import Big from "big.js"; +import { BigNumber } from "ethers"; + +const calculateTrade = ( + poolA: BigNumber, + poolB: BigNumber, + tradeInput: BigNumber +) => { + const _poolA = new Big(poolA.toString()); + const _poolB = new Big(poolB.toString()); + const input = new Big(tradeInput.toString()); + + console.log("poolA: ", poolA.toString()); + console.log("poolB: ", poolB.toString()); + // constant prduct is the starting value of the A side of the pool + const CP = new Big(_poolA.toString()); + + const marketPrice = _poolA.div(_poolB); + const newPoolA = _poolA.add(input); + + console.log( + "new pool => ", + newPoolA.toString(), + _poolA.toString(), + input.toString(), + newPoolA.cmp(_poolA) + ); + + const x = CP.div(newPoolA); + console.log("x => ", x.toString()); + const newPoolB = x.mul(_poolB); + const recieve = _poolB.sub(newPoolB); + + console.log( + "=> ", + recieve.toString(), + _poolB.toString(), + newPoolB.toString() + ); + + const newMarketPrice = input.div(recieve); + + const priceDifference = newMarketPrice.sub(marketPrice); + + const priceImpact = priceDifference.div(marketPrice).mul(100); + + return { + oldMarketPrice: marketPrice.toString(), + newMarketPrice: newMarketPrice.toString(), + recieve: recieve.toString(), + priceImpact: priceImpact.toString(), + }; +}; + +describe("playground tests", () => { + it("creates a pair", async () => { + const context: UniswapPairContextForChainId = { + fromTokenContractAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", + toTokenContractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + ethereumAddress: MockEthereumAddress(), + chainId: ChainId.MAINNET, + settings: new UniswapPairSettings({ + uniswapVersions: [UniswapVersion.v2], + // cloneUniswapContractDetails: { + // v2Override: { + // routerAddress: "", + // factoryAddress: "", + // pairAddress: "", + // }, + // }, + }), + }; + + // console.log("context: ", context); + const uniswapPair = new UniswapPair(context); + + const factory = await uniswapPair.createFactory(); + // console.log("factory: ", factory); + + const tradeCtx = await factory.trade("1"); + // console.log("trade ctx: ", tradeCtx); + + const pairFactory = new UniswapPairContractFactoryPublicV2({ + chainId: ChainId.MAINNET, + }); + + const pairAddress = await pairFactory.getPair( + context.fromTokenContractAddress, + context.toTokenContractAddress + ); + + console.log("pair addr:", pairAddress); + + const pairContract = new UniswapPairContractV2( + uniswapPair.provider(), + pairAddress + ); + + const reserves = await pairContract.getReserves(); + console.log("reserves: ", reserves); + console.log("timestamp: ", reserves.timestamp); + + // const tradeData = calculateTrade( + // reserves.tokenA, + // reserves.tokenB, + // BigNumber.from(100000) + // // BigNumber.from(1000), + // // BigNumber.from(1000), + // // BigNumber.from(1000) + // ); + + // console.log("trade data: ", tradeData); + // pairContract.subsribeSwap((aIn, aOut, bIn, bOut, to) => { + // console.log("got event: ", aIn, aOut, bIn, bOut); + // }); + + // tradeCtx.quoteChanged$.subscribe({ + // next(ctx) { console.log("quote changed!", ctx) }, + // error(err: Error) { console.log("there was an error", err) }, + // complete() { + // console.log("completed") + // } + // }) + + // expect(factory.fromToken).toEqual(ETH.MAINNET()); + // expect(factory.toToken).toEqual(MOCKFUN()); + }); +}); diff --git a/src/factories/pair/uniswap-pair.calculator.ts b/src/factories/pair/uniswap-pair.calculator.ts new file mode 100644 index 0000000..80b2fac --- /dev/null +++ b/src/factories/pair/uniswap-pair.calculator.ts @@ -0,0 +1,112 @@ +import { EthersProvider } from "../.."; +import { + PairReserves, + UniswapPairContractV2, +} from "./v2/uniswap-pair-contract.v2"; +import { UniswapPairContractFactoryPublicV2 } from "./v2/uniswap-pair-contract.factory.public.v2"; +import BigNumber from "bignumber.js"; +import { Quote } from "./models/trade-calculator"; +import Big from "big.js"; + +export class UniswapPairCalculator { + pairFactory: UniswapPairContractFactoryPublicV2; + provider: EthersProvider; + pairContract?: UniswapPairContractV2; + fromAddr: string; + toAddr: string; + reserves?: PairReserves; + + constructor(provider: EthersProvider, fromAddr: string, toAddr: string) { + this.pairFactory = new UniswapPairContractFactoryPublicV2( + provider._providerContext + ); + this.provider = provider; + this.fromAddr = fromAddr; + this.toAddr = toAddr; + } + + public async init() { + const pairAddress = await this.pairFactory.getPair( + this.fromAddr, + this.toAddr + ); + this.pairContract = new UniswapPairContractV2(this.provider, pairAddress); + // refresh reserves any time there is a swap on the pair contract + this.pairContract.subsribeSwap(this.setReserves); + await this.setReserves(); + } + + public async setReserves() { + if (this.pairContract) { + this.reserves = await this.pairContract.getReserves(); + } + } + + public cleanup() { + if (this.pairContract) { + this.pairContract.removeSwapListeners(); + } + } + + private calculateTrade( + poolA: BigNumber, + poolB: BigNumber, + tradeInput: BigNumber + ): Quote { + const _poolA = new Big(poolA.toString()); + const _poolB = new Big(poolB.toString()); + const input = new Big(tradeInput.toString()); + + // constant prduct is the starting value of the A side of the pool + const CP = new Big(_poolA.toString()); + + const marketPrice = _poolA.div(_poolB); + const newPoolA = _poolA.add(input); + const newPoolB = CP.div(newPoolA).mul(_poolB); + const recieve = _poolB.sub(newPoolB); + const newMarketPrice = input.div(recieve); + + const priceDifference = newMarketPrice.sub(marketPrice); + + const priceImpact = priceDifference.div(marketPrice).mul(100); + + return { + oldMarketPrice: marketPrice, + newMarketPrice: newMarketPrice, + recieve: recieve, + priceImpact: priceImpact, + }; + } + + public quote(input: BigNumber) { + if ( + !this.reserves || + !this.reserves.byAddress[this.fromAddr] || + !this.reserves.byAddress[this.toAddr] + ) { + throw new Error("invalid reserves"); + } + + return this.calculateTrade( + this.reserves.byAddress[this.fromAddr], + this.reserves.byAddress[this.toAddr], + input + ); + } + + public reverseQuote(input: BigNumber) { + if ( + !this.reserves || + !this.reserves.byAddress[this.fromAddr] || + !this.reserves.byAddress[this.toAddr] + ) { + throw new Error("invalid reserves"); + } + + return this.calculateTrade( + this.reserves.byAddress[this.toAddr], + this.reserves.byAddress[this.fromAddr], + input + ); + } +} diff --git a/src/factories/pair/uniswap-pair.factory.spec.ts b/src/factories/pair/uniswap-pair.factory.spec.ts index 8016b7e..6113f6c 100644 --- a/src/factories/pair/uniswap-pair.factory.spec.ts +++ b/src/factories/pair/uniswap-pair.factory.spec.ts @@ -1,3 +1,4 @@ +import { BigNumber } from "bignumber.js"; import { ChainId, ErrorCodes, @@ -5,23 +6,25 @@ import { UniswapError, UniswapPairFactory, UniswapPairSettings, -} from '../..'; -import { CoinGecko } from '../../coin-gecko'; -import { UniswapVersion } from '../../enums/uniswap-version'; -import { EthersProvider } from '../../ethers-provider'; -import { MockEthereumAddress } from '../../mocks/ethereum-address.mock'; -import { MOCKFUN } from '../../mocks/fun-token.mock'; -import { MOCK_PROVIDER_URL } from '../../mocks/provider-url.mock'; -import { MOCKREP } from '../../mocks/rep-token.mock'; -import { TradeDirection } from './models/trade-direction'; -import { UniswapPairFactoryContext } from './models/uniswap-pair-factory-context'; - -describe('UniswapPairFactory', () => { + UniswapPair, +} from "../.."; +import { CoinGecko } from "../../coin-gecko"; +import { UniswapVersion } from "../../enums/uniswap-version"; +import { EthersProvider } from "../../ethers-provider"; +import { MockEthereumAddress } from "../../mocks/ethereum-address.mock"; +import { MOCKFUN } from "../../mocks/fun-token.mock"; +import { MOCK_PROVIDER_URL } from "../../mocks/provider-url.mock"; +import { MOCKREP } from "../../mocks/rep-token.mock"; +import { TradeDirection } from "./models/trade-direction"; +import { UniswapPairFactoryContext } from "./models/uniswap-pair-factory-context"; +import { UniswapPairContextForChainId } from "./models/uniswap-pair-contexts"; + +describe("UniswapPairFactory", () => { const ethersProvider = new EthersProvider({ chainId: ChainId.MAINNET, providerUrl: MOCK_PROVIDER_URL(), }); - describe('erc20 > erc20', () => { + describe("erc20 > erc20", () => { const uniswapPairFactoryContext: UniswapPairFactoryContext = { fromToken: MOCKFUN(), toToken: MOCKREP(), @@ -35,30 +38,30 @@ describe('UniswapPairFactory', () => { uniswapPairFactoryContext ); - it('`toToken` should retun correctly', () => { + it("`toToken` should retun correctly", () => { expect(uniswapPairFactory.toToken).toEqual( uniswapPairFactoryContext.toToken ); }); - it('`fromToken` should retun correctly', () => { + it("`fromToken` should retun correctly", () => { expect(uniswapPairFactory.fromToken).toEqual( uniswapPairFactoryContext.fromToken ); }); - describe('trade', () => { - it('should return trade info', async () => { - const result = await uniswapPairFactory.trade('1'); + describe("trade", () => { + it("should return trade info", async () => { + const result = await uniswapPairFactory.trade("1"); expect(result).not.toBeUndefined(); }); }); - describe('findBestRoute', () => { + describe("findBestRoute", () => { describe(TradeDirection.input, () => { - it('should return the best route', async () => { + it("should return the best route", async () => { const result = await uniswapPairFactory.findBestRoute( - '1', + "1", TradeDirection.input ); expect(result).not.toBeUndefined(); @@ -66,9 +69,9 @@ describe('UniswapPairFactory', () => { }); describe(TradeDirection.output, () => { - it('should return the best route', async () => { + it("should return the best route", async () => { const result = await uniswapPairFactory.findBestRoute( - '1', + "1", TradeDirection.output ); expect(result).not.toBeUndefined(); @@ -76,12 +79,12 @@ describe('UniswapPairFactory', () => { }); }); - describe('findAllPossibleRoutesWithQuote', () => { + describe("findAllPossibleRoutesWithQuote", () => { describe(TradeDirection.input, () => { - it('should return all possible routes with quotes', async () => { + it("should return all possible routes with quotes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutesWithQuote( - '1', + "1", TradeDirection.input ); expect(result).not.toBeUndefined(); @@ -89,10 +92,10 @@ describe('UniswapPairFactory', () => { }); describe(TradeDirection.output, () => { - it('should return all possible routes with quotes', async () => { + it("should return all possible routes with quotes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutesWithQuote( - '1', + "1", TradeDirection.output ); expect(result).not.toBeUndefined(); @@ -100,29 +103,29 @@ describe('UniswapPairFactory', () => { }); }); - describe('findAllPossibleRoutes', () => { - it('should return all possible routes', async () => { + describe("findAllPossibleRoutes", () => { + it("should return all possible routes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutes(); expect(result).not.toBeUndefined(); }); }); - describe('allowance', () => { - describe('v2', () => { - it('should return more then 0', async () => { + describe("allowance", () => { + describe("v2", () => { + it("should return more then 0", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: MOCKREP(), - ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', + ethereumAddress: "0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba", settings: new UniswapPairSettings(), ethersProvider, }); const result = await factory.allowance(UniswapVersion.v2); - expect(result).not.toEqual('0x00'); + expect(result).not.toEqual("0x00"); }); - it('should return 0 allowance', async () => { + it("should return 0 allowance", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: MOCKFUN(), @@ -132,25 +135,25 @@ describe('UniswapPairFactory', () => { }); const result = await factory.allowance(UniswapVersion.v2); - expect(result).toEqual('0x00'); + expect(result).toEqual("0x00"); }); }); - describe('v3', () => { - xit('should return more then 0', async () => { + describe("v3", () => { + xit("should return more then 0", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: MOCKREP(), - ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', + ethereumAddress: "0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba", settings: new UniswapPairSettings(), ethersProvider, }); const result = await factory.allowance(UniswapVersion.v3); - expect(result).not.toEqual('0x00'); + expect(result).not.toEqual("0x00"); }); - it('should return 0 allowance', async () => { + it("should return 0 allowance", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: MOCKFUN(), @@ -160,45 +163,45 @@ describe('UniswapPairFactory', () => { }); const result = await factory.allowance(UniswapVersion.v3); - expect(result).toEqual('0x00'); + expect(result).toEqual("0x00"); }); }); }); - describe('generateApproveMaxAllowanceData', () => { - describe('v2', () => { - it('should generate the approve max allowance data', async () => { + describe("generateApproveMaxAllowanceData", () => { + describe("v2", () => { + it("should generate the approve max allowance data", async () => { const result = await uniswapPairFactory.generateApproveMaxAllowanceData( UniswapVersion.v2 ); expect(result).toEqual({ - data: '0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - from: '0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9', - to: '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b', - value: '0x00', + data: "0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + from: "0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9", + to: "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b", + value: "0x00", }); }); }); - describe('v2', () => { - it('should generate the approve max allowance data', async () => { + describe("v2", () => { + it("should generate the approve max allowance data", async () => { const result = await uniswapPairFactory.generateApproveMaxAllowanceData( UniswapVersion.v3 ); expect(result).toEqual({ - data: '0x095ea7b3000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - from: '0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9', - to: '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b', - value: '0x00', + data: "0x095ea7b3000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + from: "0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9", + to: "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b", + value: "0x00", }); }); }); }); }); - describe('erc20 > eth', () => { + describe("erc20 > eth", () => { const uniswapPairFactoryContext: UniswapPairFactoryContext = { fromToken: MOCKFUN(), toToken: ETH.MAINNET(), @@ -212,30 +215,30 @@ describe('UniswapPairFactory', () => { uniswapPairFactoryContext ); - it('`toToken` should retun correctly', () => { + it("`toToken` should retun correctly", () => { expect(uniswapPairFactory.toToken).toEqual( uniswapPairFactoryContext.toToken ); }); - it('`fromToken` should retun correctly', () => { + it("`fromToken` should retun correctly", () => { expect(uniswapPairFactory.fromToken).toEqual( uniswapPairFactoryContext.fromToken ); }); - describe('trade', () => { - it('should return trade info', async () => { - const result = await uniswapPairFactory.trade('1'); + describe("trade", () => { + it("should return trade info", async () => { + const result = await uniswapPairFactory.trade("1"); expect(result).not.toBeUndefined(); }); }); - describe('findBestRoute', () => { + describe("findBestRoute", () => { describe(TradeDirection.input, () => { - it('should return the best route', async () => { + it("should return the best route", async () => { const result = await uniswapPairFactory.findBestRoute( - '1', + "1", TradeDirection.input ); expect(result).not.toBeUndefined(); @@ -243,9 +246,9 @@ describe('UniswapPairFactory', () => { }); describe(TradeDirection.output, () => { - it('should return the best route', async () => { + it("should return the best route", async () => { const result = await uniswapPairFactory.findBestRoute( - '1', + "1", TradeDirection.output ); expect(result).not.toBeUndefined(); @@ -253,12 +256,12 @@ describe('UniswapPairFactory', () => { }); }); - describe('findAllPossibleRoutesWithQuote', () => { + describe("findAllPossibleRoutesWithQuote", () => { describe(TradeDirection.input, () => { - it('should return all possible routes with quotes', async () => { + it("should return all possible routes with quotes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutesWithQuote( - '1', + "1", TradeDirection.input ); expect(result).not.toBeUndefined(); @@ -266,10 +269,10 @@ describe('UniswapPairFactory', () => { }); describe(TradeDirection.output, () => { - it('should return all possible routes with quotes', async () => { + it("should return all possible routes with quotes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutesWithQuote( - '1', + "1", TradeDirection.output ); expect(result).not.toBeUndefined(); @@ -277,29 +280,29 @@ describe('UniswapPairFactory', () => { }); }); - describe('findAllPossibleRoutes', () => { - it('should return all possible routes', async () => { + describe("findAllPossibleRoutes", () => { + it("should return all possible routes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutes(); expect(result).not.toBeUndefined(); }); }); - describe('allowance', () => { - describe('v2', () => { - it('should return more then 0', async () => { + describe("allowance", () => { + describe("v2", () => { + it("should return more then 0", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: ETH.MAINNET(), - ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', + ethereumAddress: "0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba", settings: new UniswapPairSettings(), ethersProvider, }); const result = await factory.allowance(UniswapVersion.v2); - expect(result).not.toEqual('0x00'); + expect(result).not.toEqual("0x00"); }); - it('should return 0 allowance', async () => { + it("should return 0 allowance", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: ETH.MAINNET(), @@ -309,25 +312,25 @@ describe('UniswapPairFactory', () => { }); const result = await factory.allowance(UniswapVersion.v2); - expect(result).toEqual('0x00'); + expect(result).toEqual("0x00"); }); }); - describe('v3', () => { - xit('should return more then 0', async () => { + describe("v3", () => { + xit("should return more then 0", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKFUN(), toToken: ETH.MAINNET(), - ethereumAddress: '0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba', + ethereumAddress: "0x5ab9d116a53ef41063e3eae26a7ebe736720e9ba", settings: new UniswapPairSettings(), ethersProvider, }); const result = await factory.allowance(UniswapVersion.v3); - expect(result).not.toEqual('0x00'); + expect(result).not.toEqual("0x00"); }); - it('should return 0 allowance', async () => { + it("should return 0 allowance", async () => { const factory = new UniswapPairFactory(new CoinGecko(), { fromToken: MOCKREP(), toToken: ETH.MAINNET(), @@ -337,45 +340,45 @@ describe('UniswapPairFactory', () => { }); const result = await factory.allowance(UniswapVersion.v3); - expect(result).toEqual('0x00'); + expect(result).toEqual("0x00"); }); }); }); - describe('generateApproveMaxAllowanceData', () => { - describe('v2', () => { - it('should generate the approve max allowance data', async () => { + describe("generateApproveMaxAllowanceData", () => { + describe("v2", () => { + it("should generate the approve max allowance data", async () => { const result = await uniswapPairFactory.generateApproveMaxAllowanceData( UniswapVersion.v2 ); expect(result).toEqual({ - data: '0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - from: '0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9', - to: '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b', - value: '0x00', + data: "0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + from: "0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9", + to: "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b", + value: "0x00", }); }); }); - describe('v3', () => { - it('should generate the approve max allowance data', async () => { + describe("v3", () => { + it("should generate the approve max allowance data", async () => { const result = await uniswapPairFactory.generateApproveMaxAllowanceData( UniswapVersion.v3 ); expect(result).toEqual({ - data: '0x095ea7b3000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - from: '0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9', - to: '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b', - value: '0x00', + data: "0x095ea7b3000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + from: "0xB1E6079212888f0bE0cf55874B2EB9d7a5e02cD9", + to: "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b", + value: "0x00", }); }); }); }); }); - describe('eth > erc20', () => { + describe("eth > erc20", () => { const uniswapPairFactoryContext: UniswapPairFactoryContext = { fromToken: ETH.MAINNET(), toToken: MOCKFUN(), @@ -389,30 +392,30 @@ describe('UniswapPairFactory', () => { uniswapPairFactoryContext ); - it('`toToken` should retun correctly', () => { + it("`toToken` should retun correctly", () => { expect(uniswapPairFactory.toToken).toEqual( uniswapPairFactoryContext.toToken ); }); - it('`fromToken` should retun correctly', () => { + it("`fromToken` should retun correctly", () => { expect(uniswapPairFactory.fromToken).toEqual( uniswapPairFactoryContext.fromToken ); }); - describe('trade', () => { - it('should return trade info', async () => { - const result = await uniswapPairFactory.trade('1'); + describe("trade", () => { + it("should return trade info", async () => { + const result = await uniswapPairFactory.trade("1"); expect(result).not.toBeUndefined(); }); }); - describe('findBestRoute', () => { + describe("findBestRoute", () => { describe(TradeDirection.input, () => { - it('should return the best route', async () => { + it("should return the best route", async () => { const result = await uniswapPairFactory.findBestRoute( - '1', + "1", TradeDirection.input ); expect(result).not.toBeUndefined(); @@ -420,9 +423,9 @@ describe('UniswapPairFactory', () => { }); describe(TradeDirection.output, () => { - it('should return the best route', async () => { + it("should return the best route", async () => { const result = await uniswapPairFactory.findBestRoute( - '1', + "1", TradeDirection.output ); expect(result).not.toBeUndefined(); @@ -430,12 +433,12 @@ describe('UniswapPairFactory', () => { }); }); - describe('findAllPossibleRoutesWithQuote', () => { + describe("findAllPossibleRoutesWithQuote", () => { describe(TradeDirection.input, () => { - it('should return all possible routes with quotes', async () => { + it("should return all possible routes with quotes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutesWithQuote( - '1', + "1", TradeDirection.input ); expect(result).not.toBeUndefined(); @@ -443,10 +446,10 @@ describe('UniswapPairFactory', () => { }); describe(TradeDirection.output, () => { - it('should return all possible routes with quotes', async () => { + it("should return all possible routes with quotes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutesWithQuote( - '1', + "1", TradeDirection.output ); expect(result).not.toBeUndefined(); @@ -454,58 +457,58 @@ describe('UniswapPairFactory', () => { }); }); - describe('findAllPossibleRoutes', () => { - it('should return all possible routes', async () => { + describe("findAllPossibleRoutes", () => { + it("should return all possible routes", async () => { const result = await uniswapPairFactory.findAllPossibleRoutes(); expect(result).not.toBeUndefined(); }); }); - describe('allowance', () => { - describe('v2', () => { - it('should always return max hex', async () => { + describe("allowance", () => { + describe("v2", () => { + it("should always return max hex", async () => { const result = await uniswapPairFactory.allowance(UniswapVersion.v2); expect(result).toEqual( - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ); }); }); - describe('v3', () => { - it('should always return max hex', async () => { + describe("v3", () => { + it("should always return max hex", async () => { const result = await uniswapPairFactory.allowance(UniswapVersion.v3); expect(result).toEqual( - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ); }); }); }); - describe('generateApproveMaxAllowanceData', () => { - describe('v2', () => { - it('should throw when generating the approve max allowance data', async () => { + describe("generateApproveMaxAllowanceData", () => { + describe("v2", () => { + it("should throw when generating the approve max allowance data", async () => { await expect( uniswapPairFactory.generateApproveMaxAllowanceData( UniswapVersion.v2 ) ).rejects.toThrowError( new UniswapError( - 'You do not need to generate approve uniswap allowance when doing eth > erc20', + "You do not need to generate approve uniswap allowance when doing eth > erc20", ErrorCodes.generateApproveMaxAllowanceDataNotAllowed ) ); }); }); - describe('v3', () => { - it('should throw when generating the approve max allowance data', async () => { + describe("v3", () => { + it("should throw when generating the approve max allowance data", async () => { await expect( uniswapPairFactory.generateApproveMaxAllowanceData( UniswapVersion.v3 ) ).rejects.toThrowError( new UniswapError( - 'You do not need to generate approve uniswap allowance when doing eth > erc20', + "You do not need to generate approve uniswap allowance when doing eth > erc20", ErrorCodes.generateApproveMaxAllowanceDataNotAllowed ) ); @@ -514,3 +517,36 @@ describe('UniswapPairFactory', () => { }); }); }); + +describe.only("trade_calculator", () => { + const ethersProvider = new EthersProvider({ + chainId: ChainId.MAINNET, + providerUrl: MOCK_PROVIDER_URL(), + }); + + const uniswapPairFactoryContext: UniswapPairFactoryContext = { + fromToken: { + ...ETH.MAINNET(), + contractAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + }, + toToken: MOCKFUN(), + ethereumAddress: MockEthereumAddress(), + settings: new UniswapPairSettings(), + ethersProvider, + }; + + const factory = new UniswapPairFactory( + new CoinGecko(), + uniswapPairFactoryContext + ); + + it("works", async () => { + const calc = await factory.newPairCalculator(); + let output = calc.quote(new BigNumber("1000000")); + expect(output.recieve.cmp("0")).toBe(1); + await calc.setReserves(); + output = calc.reverseQuote(new BigNumber("2000000")); + expect(output.recieve.cmp("0")).toBe(1); + calc.cleanup(); + }); +}); diff --git a/src/factories/pair/uniswap-pair.factory.ts b/src/factories/pair/uniswap-pair.factory.ts index 361ecbc..cb24dde 100644 --- a/src/factories/pair/uniswap-pair.factory.ts +++ b/src/factories/pair/uniswap-pair.factory.ts @@ -1,4 +1,3 @@ -import Big from "big.js"; import BigNumber from "bignumber.js"; import { Subject } from "rxjs"; import { CoinGecko } from "../../coin-gecko"; @@ -26,12 +25,7 @@ import { TradeContext } from "./models/trade-context"; import { TradeDirection } from "./models/trade-direction"; import { Transaction } from "./models/transaction"; import { UniswapPairFactoryContext } from "./models/uniswap-pair-factory-context"; -import { UniswapPairContractFactoryPublicV2 } from "./v2/uniswap-pair-contract.factory.public.v2"; -import { - PairReserves, - UniswapPairContractV2, -} from "./v2/uniswap-pair-contract.v2"; - +import { UniswapPairCalculator } from "./uniswap-pair.calculator"; export class UniswapPairFactory { private _fromTokenFactory = new TokenFactory( this._uniswapPairFactoryContext.fromToken.contractAddress, @@ -310,66 +304,18 @@ export class UniswapPairFactory { }); } - private async getPairReserves( - fromToken: string, - toToken: string - ): Promise { - const network = this._uniswapPairFactoryContext.ethersProvider.network(); - const pairFactory = new UniswapPairContractFactoryPublicV2({ - chainId: network.chainId, - }); - - const pairAddress = await pairFactory.getPair(fromToken, toToken); + public async newPairCalculator() { + const fromToken = this._uniswapPairFactoryContext.fromToken.contractAddress; + const toToken = this._uniswapPairFactoryContext.toToken.contractAddress; - const pairContract = new UniswapPairContractV2( + const calc = new UniswapPairCalculator( this._uniswapPairFactoryContext.ethersProvider, - pairAddress + fromToken, + toToken ); - return pairContract.getReserves(); - } - - private calculateTrade( - poolA: BigNumber, - poolB: BigNumber, - tradeInput: BigNumber - ) { - const _poolA = new Big(poolA.toString()); - const _poolB = new Big(poolB.toString()); - const input = new Big(tradeInput.toString()); - - // constant prduct is the starting value of the A side of the pool - const CP = new Big(_poolA.toString()); - - const marketPrice = _poolA.div(_poolB); - const newPoolA = _poolA.add(input); - const newPoolB = CP.div(newPoolA).mul(_poolB); - const recieve = _poolB.sub(newPoolB); - const newMarketPrice = input.div(recieve); - - const priceDifference = newMarketPrice.sub(marketPrice); - - const priceImpact = priceDifference.div(marketPrice).mul(100); - - return { - oldMarketPrice: marketPrice, - newMarketPrice: newMarketPrice, - recieve: recieve, - priceImpact: priceImpact, - }; - } - - public async newTradeCalculator(fromToken: string, toToken: string) { - const reserves = await this.getPairReserves(fromToken, toToken); - return (from: string, to: string, tradeInput: BigNumber) => { - // you can change the order of the token addresses - // but cannot use different tokens - if (!reserves[from] || !reserves[to]) { - throw new Error("unknown token address"); - } - - this.calculateTrade(reserves[from], reserves[to], tradeInput); - }; + await calc.init(); + return calc; } /** diff --git a/src/factories/pair/v2/uniswap-pair-contract.v2.ts b/src/factories/pair/v2/uniswap-pair-contract.v2.ts index 233e391..7bc8c0c 100644 --- a/src/factories/pair/v2/uniswap-pair-contract.v2.ts +++ b/src/factories/pair/v2/uniswap-pair-contract.v2.ts @@ -4,8 +4,9 @@ import { EthersProvider } from "../../../ethers-provider"; import { UniswapContractContextV2 } from "../../../uniswap-contract-context/uniswap-contract-context-v2"; export interface PairReserves { - tokenA: BigNumber; - tokenB: BigNumber; + byAddress: { + [key: string]: BigNumber; + }; timestamp: number; } @@ -32,15 +33,20 @@ export class UniswapPairContractV2 { public async getReserves(): Promise { const resp = await this._uniswapPair.getReserves(); + const token0Addr = await this._uniswapPair.token0(); + const token1Addr = await this._uniswapPair.token1(); + return { - tokenA: new BigNumber(resp[0].toString()), - tokenB: new BigNumber(resp[1].toString()), + byAddress: { + [token0Addr]: new BigNumber(resp[0].toString()), + [token1Addr]: new BigNumber(resp[1].toString()), + }, timestamp: resp[2], }; } public subsribeSwap(listener: SwapListener) { - this._uniswapPair.addListener("Swap", listener); + this._uniswapPair.on("Swap", listener); } public removeSwapListeners() {