diff --git a/package.json b/package.json index 8583e93..d93b250 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@dfyn/sdk", + "name": "dfyn-sdk-v2", "license": "MIT", - "version": "0.0.8", + "version": "0.3.91", "description": "🛠 An SDK for building applications on top of Dfyn.", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -13,7 +13,7 @@ "dfyn", "Multi-chain AMM" ], - "module": "dist/sdk.esm.js", + "module": "dist/dfyn-sdk-v2.esm.js", "scripts": { "lint": "tsdx lint src test", "build": "tsdx build", @@ -22,6 +22,7 @@ "prepublishOnly": "tsdx build" }, "dependencies": { + "@ethersproject/abi": "^5.0.12", "@uniswap/v2-core": "^1.0.0", "big.js": "^5.2.2", "decimal.js-light": "^2.5.0", @@ -56,4 +57,4 @@ "semi": false, "singleQuote": true } -} \ No newline at end of file +} diff --git a/src/MasterDeployer.ts b/src/MasterDeployer.ts new file mode 100644 index 0000000..1c7d3d7 --- /dev/null +++ b/src/MasterDeployer.ts @@ -0,0 +1,37 @@ +import { defaultAbiCoder, Interface } from "@ethersproject/abi"; +import MasterDeployerAbi from "./abis/MasterDeployer.json" +import { TICK_SPACINGS, V2_FACTORY_ADDRESS } from "./constants"; +import { Pool } from "./entities"; +import { MethodParameters, toHex } from "./utils"; + +export abstract class MasterDeployer { + public static INTERFACE: Interface = new Interface(MasterDeployerAbi) + + /** + * Cannot be constructed. + */ + private constructor() {} + + private static encodeCreate(pool: Pool): string { + console.log([pool.token0.address, pool.token1.address, pool.sqrtRatioX96, TICK_SPACINGS[pool.fee]]) + const deployData = defaultAbiCoder.encode( + ["address", "address", "uint160", "uint24"], + [pool.token0.address, pool.token1.address, pool.sqrtRatioX96.toString(), TICK_SPACINGS[pool.fee]] + ); + console.log(deployData) + const x= this.INTERFACE.encodeFunctionData('deployPool', [ + V2_FACTORY_ADDRESS[pool.token0.chainId], + deployData + ]) + console.log(x) + return x + } + + public static createCallParameters(pool: Pool): MethodParameters { + return { + calldata: this.encodeCreate(pool), + value: toHex(0) + } + } + +} \ No newline at end of file diff --git a/src/abis/ConcentratedPoolManager.json b/src/abis/ConcentratedPoolManager.json new file mode 100644 index 0000000..dd0c091 --- /dev/null +++ b/src/abis/ConcentratedPoolManager.json @@ -0,0 +1,902 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_masterDeployer", + "type": "address" + }, + { + "internalType": "address", + "name": "_wETH", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "positionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + } + ], + "name": "DecreaseLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "positionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + } + ], + "name": "IncreaseLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "domainSeperator", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_ALL_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "unwrapVault", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "minimumOut0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumOut1", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "token0Amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "unwrapVault", + "type": "bool" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint256", + "name": "token0amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "masterDeployer", + "outputs": [ + { + "internalType": "contract IMasterDeployer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IConcentratedLiquidityPool", + "name": "pool", + "type": "address" + }, + { + "internalType": "int24", + "name": "lowerOld", + "type": "int24" + }, + { + "internalType": "int24", + "name": "lower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "upperOld", + "type": "int24" + }, + { + "internalType": "int24", + "name": "upper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Desired", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Desired", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "native", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "minLiquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "positionId", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "_positionId", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "native", + "type": "bool" + } + ], + "name": "mintCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nftCount", + "outputs": [ + { + "internalType": "uint128", + "name": "minted", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "burned", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "noncesForAll", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "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": "permitAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "positionFees", + "outputs": [ + { + "internalType": "uint256", + "name": "token0amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "contract IConcentratedLiquidityPool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "int24", + "name": "lower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "upper", + "type": "int24" + }, + { + "internalType": "uint32", + "name": "latestAddition", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "contract IVaultMinimal", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/abis/LimitOrderManager.json b/src/abis/LimitOrderManager.json new file mode 100644 index 0000000..011f3ad --- /dev/null +++ b/src/abis/LimitOrderManager.json @@ -0,0 +1,490 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_masterDeployer", + "type": "address" + }, + { + "internalType": "address", + "name": "_weth", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ExcessRebate", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyOwner", + "type": "error" + }, + { + "inputs": [], + "name": "TickOutOfBounds", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "CancelLimitOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimLimitOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "CreateLImitOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isRebate", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "amount", + "type": "uint24" + } + ], + "name": "LimitOrderChargeUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_forwarder", + "type": "address" + }, + { + "internalType": "uint24", + "name": "_fee", + "type": "uint24" + }, + { + "internalType": "bool", + "name": "_authorize", + "type": "bool" + } + ], + "name": "authorizeForwarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "unwrapVault", + "type": "bool" + } + ], + "name": "cancelLimitOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "unwrapVault", + "type": "bool" + } + ], + "name": "claimLimitOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IConcentratedLiquidityPool", + "name": "pool", + "type": "address" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "lowerOld", + "type": "int24" + }, + { + "internalType": "int24", + "name": "upperOld", + "type": "int24" + }, + { + "internalType": "bool", + "name": "native", + "type": "bool" + }, + { + "internalType": "uint128", + "name": "amountIn", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "address", + "name": "recepient", + "type": "address" + } + ], + "name": "createLimitOrder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "forwarder", + "outputs": [ + { + "internalType": "bool", + "name": "isWhitelisted", + "type": "bool" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "forwarderMeta", + "outputs": [ + { + "internalType": "uint128", + "name": "forwarderRebateAmount", + "type": "uint128" + }, + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "native", + "type": "bool" + } + ], + "name": "limitOrderCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "limitOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "limitOrderToken", + "outputs": [ + { + "internalType": "contract LimitOrderToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "limitOrders", + "outputs": [ + { + "internalType": "contract IConcentratedLiquidityPool", + "name": "pool", + "type": "address" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "enum ILimitOrderStruct.LimitOrderStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "uint128", + "name": "amountIn", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amountOut", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "chargeAmount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "rebateAmount", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sqrtpriceX96", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimableGrowth0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimableGrowth1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "masterDeployer", + "outputs": [ + { + "internalType": "contract IMasterDeployer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint24", + "name": "_rate", + "type": "uint24" + }, + { + "internalType": "address", + "name": "_pool", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isRebate", + "type": "bool" + } + ], + "name": "setLimitOrderCharge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "contract IVaultMinimal", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/abis/MasterDeployer.json b/src/abis/MasterDeployer.json new file mode 100644 index 0000000..f9e87fe --- /dev/null +++ b/src/abis/MasterDeployer.json @@ -0,0 +1,366 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_dfynFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_dfynFeeTo", + "type": "address" + }, + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_limitOrderFee", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidDfynFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLimitOrderFee", + "type": "error" + }, + { + "inputs": [], + "name": "NotWhitelisted", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "AddToWhitelist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "deployData", + "type": "bytes" + } + ], + "name": "DeployPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "dfynFee", + "type": "uint256" + } + ], + "name": "DfynFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "limitOrderFee", + "type": "uint256" + } + ], + "name": "LimitOrderFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "RemoveFromWhitelist", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "addToWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_deployData", + "type": "bytes" + } + ], + "name": "deployPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dfynFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dfynFeeTo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "limitOrderFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "pools", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "removeFromWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_isLimit", + "type": "bool" + } + ], + "name": "setDfynFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_dfynFeeTo", + "type": "address" + } + ], + "name": "setDfynFeeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelistedFactories", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/abis/Multicall.json b/src/abis/Multicall.json new file mode 100644 index 0000000..64bf70d --- /dev/null +++ b/src/abis/Multicall.json @@ -0,0 +1,110 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "a", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "b", + "type": "uint256" + } + ], + "name": "functionThatReturnsTuple", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "a", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "b", + "type": "uint256" + } + ], + "internalType": "struct MulticallMock.Tuple", + "name": "tuple", + "type": "tuple" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "error", + "type": "string" + } + ], + "name": "functionThatRevertsWithError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "functionThatRevertsWithoutError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "paid", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pays", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "returnSender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/src/abis/SwapRouter.json b/src/abis/SwapRouter.json new file mode 100644 index 0000000..fc3f834 --- /dev/null +++ b/src/abis/SwapRouter.json @@ -0,0 +1,1377 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_factoryV2", + "type": "address" + }, + { + "internalType": "address", + "name": "factoryV3", + "type": "address" + }, + { + "internalType": "address", + "name": "_positionManager", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9V1", + "type": "address" + }, + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_masterDeployer", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WETH9V1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "approveMax", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "approveMaxMinusOne", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "approveZeroThenMax", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "approveZeroThenMaxMinusOne", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "callPositionManager", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + } + ], + "internalType": "struct IV3SwapRouter.ExactInputParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "unwrapVault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + } + ], + "internalType": "struct IDfynRouter.Path[]", + "name": "path", + "type": "tuple[]" + } + ], + "internalType": "struct IDfynRouter.ExactInputParamsDfyn", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputDfyn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IV3SwapRouter.ExactInputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IDfynRouter.ExactInputSingleParamsDfyn", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputSingleDfyn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IDfynRouter.ExactInputSingleParamsDfyn", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputSingleWithNativeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "unwrapVault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + } + ], + "internalType": "struct IDfynRouter.Path[]", + "name": "path", + "type": "tuple[]" + } + ], + "internalType": "struct IDfynRouter.ExactInputParamsDfyn", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputWithNativeToken", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMaximum", + "type": "uint256" + } + ], + "internalType": "struct IV3SwapRouter.ExactOutputParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMaximum", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IV3SwapRouter.ExactOutputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactOutputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factoryV2", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "getApprovalType", + "outputs": [ + { + "internalType": "enum IApproveAndCall.ApprovalType", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + } + ], + "internalType": "struct IApproveAndCall.IncreaseLiquidityParams", + "name": "params", + "type": "tuple" + } + ], + "name": "increaseLiquidity", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "masterDeployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint256", + "name": "amount0Min", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Min", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "internalType": "struct IApproveAndCall.MintParams", + "name": "params", + "type": "tuple" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "previousBlockhash", + "type": "bytes32" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "positionManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "pull", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "refundETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "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": "selfPermit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitAllowed", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitAllowedIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "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": "selfPermitIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "swapCallBack", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "swapExactTokensForTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMax", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "swapTokensForExactTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "onVault", + "type": "bool" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeBips", + "type": "uint256" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "sweepTokenWithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feeBips", + "type": "uint256" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "sweepTokenWithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "uniswapV3SwapCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "unwrapWETH9", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + } + ], + "name": "unwrapWETH9", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "v1", + "type": "bool" + } + ], + "name": "unwrapWETH9Legacy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feeBips", + "type": "uint256" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "unwrapWETH9WithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeBips", + "type": "uint256" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "unwrapWETH9WithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "wrapETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "v1", + "type": "bool" + } + ], + "name": "wrapETHLegacy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/src/concentratedPoolManager.ts b/src/concentratedPoolManager.ts new file mode 100644 index 0000000..6003566 --- /dev/null +++ b/src/concentratedPoolManager.ts @@ -0,0 +1,347 @@ +import { Interface } from "@ethersproject/abi"; +import invariant from "tiny-invariant"; +import ConcentratedPoolManagerAbi from "./abis/ConcentratedPoolManager.json" +import { Currency, CurrencyAmount, Percent, Pool, Position, wrappedCurrency } from "./entities"; +import { ZERO } from "./internalConstants"; +import { MethodParameters, toHex, validateAndParseAddress } from "./utils"; +import JSBI from 'jsbi' +import { ADDRESS_ZERO, BigintIsh } from "./constants"; +import { Multicall } from "./multicall"; + +export interface MintSpecificOptions { + /** + * The account that should receive the minted NFT. + */ + recipient: string + + /** + * Creates pool if not initialized before mint. + */ + createPool?: boolean +} +export interface CollectOptions { + /** + * Indicates the ID of the position to collect for. + */ + tokenId: BigintIsh + + /** + * Expected value of tokensOwed0, including as-of-yet-unaccounted-for fees/liquidity value to be burned + */ + expectedCurrencyOwed0: CurrencyAmount + + /** + * Expected value of tokensOwed1, including as-of-yet-unaccounted-for fees/liquidity value to be burned + */ + expectedCurrencyOwed1: CurrencyAmount + + /** + * The account that should receive the tokens. + */ + recipient: string + + /** + * To unwrap or not + */ + unwrapVault: boolean +} +/** + * Options for producing the calldata to exit a position. + */ +export interface RemoveLiquidityOptions { + /** + * The ID of the token to exit + */ + tokenId: BigintIsh + + /** + * The percentage of position liquidity to exit. + */ + liquidityPercentage: Percent + + /** + * How much the pool price is allowed to move. + */ + slippageTolerance: Percent + + + /** + * The account that should receive the tokens. + */ + recipient: string + + /** + * To unwrap or not + */ + unwrapVault: boolean +} + +export interface IncreaseSpecificOptions { + /** + * Indicates the ID of the position to increase liquidity for. + */ + tokenId: BigintIsh +} + +/** + * Options for producing the calldata to add liquidity. + */ +export interface CommonAddLiquidityOptions { + /** + * How much the pool price is allowed to move. + */ + slippageTolerance: Percent + + /** + * When the transaction expires, in epoch seconds. + */ + deadline: BigintIsh + + /** + * Whether to spend ether. If true, one of the pool tokens must be WETH, by default false + */ + useNative?: Currency + + // /** + // * The optional permit parameters for spending token0 + // */ + // token0Permit?: PermitOptions + + // /** + // * The optional permit parameters for spending token1 + // */ + // token1Permit?: PermitOptions + + /** + * The lower and upper old + */ + lowerOldTick: number + + /** + * The lower and upper old + */ + upperOldTick: number + +} + +export type MintOptions = CommonAddLiquidityOptions & MintSpecificOptions +export type IncreaseOptions = CommonAddLiquidityOptions & IncreaseSpecificOptions + +export type AddLiquidityOptions = MintOptions | IncreaseOptions + +export type RemoveAndAddLiquidityOptions = AddLiquidityOptions & RemoveLiquidityOptions + +export interface SafeTransferOptions { + /** + * The account sending the NFT. + */ + sender: string + + /** + * The account that should receive the NFT. + */ + recipient: string + + /** + * The id of the token being sent. + */ + tokenId: BigintIsh + /** + * The optional parameter that passes data to the `onERC721Received` call for the staker + */ + data?: string +} + +// type guard +export function isMint(options: AddLiquidityOptions): options is MintOptions { + return Object.keys(options).some(k => k === 'recipient') +} + + +export abstract class ConcentratedPoolManager { + public static INTERFACE: Interface = new Interface(ConcentratedPoolManagerAbi) + + /** + * Cannot be constructed. + */ + private constructor() { } + + public static addCallParameters(position: Position, options: AddLiquidityOptions): MethodParameters { + invariant(JSBI.greaterThan(position.liquidity, ZERO), 'ZERO_LIQUIDITY') + + let calldata: string + + // get amounts + const { amount0: amount0Desired, amount1: amount1Desired } = position.mintAmounts + + // mint + if (isMint(options)) { + calldata = + this.INTERFACE.encodeFunctionData('mint', [ + Pool.getAddress(position.pool.token0, position.pool.token1), + options.lowerOldTick, + position.tickLower, + options.upperOldTick, + position.tickUpper, + toHex(amount0Desired), + toHex(amount1Desired), + true, + "0", + "0" + ]) + + } else { + // increase + calldata = + this.INTERFACE.encodeFunctionData('mint', [ + Pool.getAddress(position.pool.token0, position.pool.token1), + options.lowerOldTick, + position.tickLower, + options.upperOldTick, + position.tickUpper, + toHex(amount0Desired), + toHex(amount1Desired), + true, + "0", + options.tokenId + ]) + } + + let value: string = toHex(0) + if (options.useNative) { + const wrapped = wrappedCurrency(options.useNative, position.pool.token0.chainId) + invariant(position.pool.token0.equals(wrapped) || position.pool.token1.equals(wrapped), 'NO_WETH') + + const wrappedValue = position.pool.token0.equals(wrapped) ? amount0Desired : amount1Desired + + value = toHex(wrappedValue) + } + + return { + calldata, + value + } + } + + private static encodeCollect(options: CollectOptions): string { + let calldata + + const tokenId = toHex(options.tokenId) + + // // // // // // // // // // // // // // // // // // // // // + // // // // // // // NATIVE SUPPORT TO BE ADDED // // // // // + // // // // // // // // // // // // // // // // // // // // // + // const involvesETH = + // options.expectedCurrencyOwed0.currency.isNative || options.expectedCurrencyOwed1.currency.isNative + const involvesETH = + false + + const recipient = validateAndParseAddress(options.recipient) + + // collect + calldata = + this.INTERFACE.encodeFunctionData('collect', [ + tokenId, + involvesETH ? ADDRESS_ZERO : recipient, + options.unwrapVault + ]) + + return calldata + } + + public static collectCallParameters(options: CollectOptions): MethodParameters { + const calldata = this.encodeCollect(options) + + return { + calldata, + value: toHex(0) + } + } + + /** + * Produces the calldata for completely or partially exiting a position + * @param position The position to exit + * @param options Additional information necessary for generating the calldata + * @returns The call parameters + */ + public static removeCallParameters(position: Position, options: RemoveLiquidityOptions): MethodParameters { + let calldata; + + const tokenId = toHex(options.tokenId) + const recipient = options.recipient + + // construct a partial position with a percentage of liquidity + const partialPosition = new Position({ + pool: position.pool, + liquidity: options.liquidityPercentage.multiply(position.liquidity).quotient, + tickLower: position.tickLower, + tickUpper: position.tickUpper + }) + invariant(JSBI.greaterThan(partialPosition.liquidity, ZERO), 'ZERO_LIQUIDITY') + + // slippage-adjusted underlying amounts + const { amount0: amount0Min, amount1: amount1Min } = partialPosition.burnAmountsWithSlippage( + options.slippageTolerance + ) + + // if (options.permit) { + // calldatas.push( + // NonfungiblePositionManager.INTERFACE.encodeFunctionData('permit', [ + // validateAndParseAddress(options.permit.spender), + // tokenId, + // toHex(options.permit.deadline), + // options.permit.v, + // options.permit.r, + // options.permit.s + // ]) + // ) + // } + + // remove liquidity + + calldata = this.INTERFACE.encodeFunctionData('burn', [ + tokenId, + partialPosition.liquidity.toString(), + recipient, + options.unwrapVault, + amount0Min.toString(), + amount1Min.toString() + ]) + + + return { + calldata, + value: toHex(0) + } + } + + /** + * Produces the calldata for removing a position and creating a new position + * @param position The position to exit + * @param options Additional information necessary for generating the calldata + * @returns The call parameters + */ + public static removeAndAddCallParameters(positionToBeRemoved: Position, positionToBeAdded: Position, options: RemoveAndAddLiquidityOptions): MethodParameters { + const removeCallParameters = this.removeCallParameters(positionToBeRemoved, { + liquidityPercentage: options.liquidityPercentage, + recipient: options.recipient, + slippageTolerance: options.slippageTolerance, + tokenId: options.tokenId, + unwrapVault: options.unwrapVault + }) + + const addCallParameters = this.addCallParameters(positionToBeAdded, { + deadline: options.deadline, + lowerOldTick: options.lowerOldTick, + recipient: options.recipient, + slippageTolerance: options.slippageTolerance, + tokenId: "0", + upperOldTick: options.upperOldTick, + useNative: options.useNative, + }) + return { + calldata: Multicall.encodeMulticall([removeCallParameters.calldata, addCallParameters.calldata]), + value: addCallParameters.value + } + } +} \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index 3034de2..579dec4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ import JSBI from 'jsbi' // exports for external consumption -export type BigintIsh = JSBI | bigint | string +export type BigintIsh = JSBI | number | string export enum ChainId { MAINNET = 1, @@ -16,7 +16,11 @@ export enum ChainId { XDAI = 100, BSC = 56, HARMONY = 1666600000, - AVALANCHE = 43114 + AVALANCHE = 43114, + BASE = 8453, + OPTIMISM = 10, + MANTLE = 5000, + ROUTER = 9600 } export enum TradeType { @@ -44,6 +48,10 @@ export const FACTORY_ADDRESS: { [chainId in ChainId]: string } = { [ChainId.HARMONY]: '0xd9820a17053d6314B20642E465a84Bf01a3D64f5', [ChainId.BSC]: '0xd9820a17053d6314B20642E465a84Bf01a3D64f5', [ChainId.AVALANCHE]: '0xd9820a17053d6314B20642E465a84Bf01a3D64f5', + [ChainId.BASE]: '', //TODO: change to base factory address + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' } export const ROUTER_ADDRESS: { [chainId in ChainId]: string } = { @@ -60,6 +68,10 @@ export const ROUTER_ADDRESS: { [chainId in ChainId]: string } = { [ChainId.HARMONY]: '0x8973792d9E8EA794E546b62c0f2295e32a6d7E48', [ChainId.BSC]: '0x2724B9497b2cF3325C6BE3ea430b3cec34B5Ef2d', [ChainId.AVALANCHE]: '0x4c28f48448720e9000907BC2611F73022fdcE1fA', + [ChainId.BASE]: '', //TODO: change to base router address + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' } // export const INIT_CODE_HASH = '0xf187ed688403aa4f7acfada758d8d53698753b998a3071b06f1b777f4330eaf3' @@ -77,11 +89,12 @@ export const INIT_CODE_HASH: { [chainId in ChainId]: string } = { [ChainId.HARMONY]: '0xd3ab2c392f54feb4b3b2a677f449b133c188ad2f1015eff3e94ea9315282c5f5', [ChainId.BSC]: '0xd3ab2c392f54feb4b3b2a677f449b133c188ad2f1015eff3e94ea9315282c5f5', [ChainId.AVALANCHE]: '0x512ce213a92fcce51fda9ba8738d5584ab111453ad8da5d2bd7d36bc97d14b5c', + [ChainId.BASE]: '', //TODO: change to base init code hash + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' } - - - export const MINIMUM_LIQUIDITY = JSBI.BigInt(1000) // exports for internal consumption @@ -104,3 +117,106 @@ export const SOLIDITY_TYPE_MAXIMA = { [SolidityType.uint8]: JSBI.BigInt('0xff'), [SolidityType.uint256]: JSBI.BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') } + +export const V2_FACTORY_ADDRESS: { [chainId in ChainId]: string } = { + [ChainId.MAINNET]: '', + [ChainId.ROPSTEN]: '', + [ChainId.RINKEBY]: '', + [ChainId.GÖRLI]: '', + [ChainId.KOVAN]: '', + [ChainId.MATIC]: '0xE3c3c8286FbbbE6851E430D84Ffac35D83286F72', + [ChainId.OKEX]: '', + [ChainId.ARBITRUM]: '0xB765B066c088539256456ff4CA3C859597DE36B3', + [ChainId.XDAI]: '', + [ChainId.FANTOM]: '', + [ChainId.HARMONY]: '', + [ChainId.BSC]: '', + [ChainId.AVALANCHE]: '', + [ChainId.BASE]: '', + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' +} + +export const V2_DEPLOYER_ADDRESS: { [chainId in ChainId]: string } = { + [ChainId.MAINNET]: '', + [ChainId.ROPSTEN]: '', + [ChainId.RINKEBY]: '', + [ChainId.GÖRLI]: '', + [ChainId.KOVAN]: '', + [ChainId.MATIC]: '0xf79a83E3f8E853D9658e8b97a83942Af80d45b85', + [ChainId.OKEX]: '', + [ChainId.ARBITRUM]: '0x201402538821Fb0cA7E0d04e37b116F11F189B2E', + [ChainId.XDAI]: '', + [ChainId.FANTOM]: '', + [ChainId.HARMONY]: '', + [ChainId.BSC]: '', + [ChainId.AVALANCHE]: '', + [ChainId.BASE]: '', + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' +} +export const V2_MASTER_DEPLOYER_ADDRESS: { [chainId in ChainId]: string } = { + [ChainId.MAINNET]: '', + [ChainId.ROPSTEN]: '', + [ChainId.RINKEBY]: '', + [ChainId.GÖRLI]: '', + [ChainId.KOVAN]: '', + [ChainId.MATIC]: '0xF9A626BB1eb10464F7E691d4070d283023910100', + [ChainId.OKEX]: '', + [ChainId.ARBITRUM]: '0x7d351b07F091b091F18e2656807Ab48276Ea07dC', + [ChainId.XDAI]: '', + [ChainId.FANTOM]: '', + [ChainId.HARMONY]: '', + [ChainId.BSC]: '', + [ChainId.AVALANCHE]: '', + [ChainId.BASE]: '', + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' +} + +export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000' + +export const V2_POOL_INIT_CODE_HASH: { [chainId in ChainId]: string } = { + [ChainId.MAINNET]: '', + [ChainId.ROPSTEN]: '', + [ChainId.RINKEBY]: '', + [ChainId.GÖRLI]: '', + [ChainId.KOVAN]: '', + [ChainId.MATIC]: '0x1a7e5ef1e1989c411ffc5bd046a9f78d9f197278d7205be2531e46a142074f42', + [ChainId.OKEX]: '', + [ChainId.ARBITRUM]: '0x5de87646b5e50974033508c1107fd77c393026d0e7b0bf353788b202e6dd3f43', + [ChainId.XDAI]: '', + [ChainId.FANTOM]: '', + [ChainId.HARMONY]: '', + [ChainId.BSC]: '', + [ChainId.AVALANCHE]: '', + [ChainId.BASE]: '', + [ChainId.OPTIMISM]: '', + [ChainId.MANTLE]: '', + [ChainId.ROUTER]: '' +} + +/** + * The default factory enabled fee amounts, denominated in hundredths of bips. + */ +export enum FeeAmount { + // LOWEST = 100, + LOW = 1500 + // MEDIUM = 3000, + // HIGH = 10000 +} + +/** + * The default factory tick spacings by fee amount. + */ +export const TICK_SPACINGS: { [amount in FeeAmount]: number } = { + // [FeeAmount.LOWEST]: 1, + [FeeAmount.LOW]: 10 + // [FeeAmount.MEDIUM]: 60, + // [FeeAmount.HIGH]: 200 +} + +export const MaxUint256 = JSBI.BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') diff --git a/src/entities/RouteWrappers.ts b/src/entities/RouteWrappers.ts new file mode 100644 index 0000000..b9c5aeb --- /dev/null +++ b/src/entities/RouteWrappers.ts @@ -0,0 +1,72 @@ +// entities/route.ts + +import { Route as V1RouteSDK, Pair } from '../index' +import { V2Route as V2RouteSDK, Pool } from '../index' +import { Protocol } from './protocol' +import { Currency, Price, Token } from '../index' +import { MixedRouteSDK } from './mixedRoute/route' +type TPool = Pair | Pool + +export interface IRoute { + protocol: Protocol + // array of pools if v3 or pairs if v2 + pools: TPool[] + path: Token[] + midPrice: Price + input: Currency + output: Currency +} + +// V1 route wrapper +export class RouteV1 + extends V1RouteSDK + implements IRoute +{ + public readonly protocol: Protocol = Protocol.V1 + public readonly pools: Pair[] + + constructor(v1Route: V1RouteSDK) { + super(v1Route.pairs, v1Route.input, v1Route.output) + this.pools = this.pairs + } +} + +// V2 route wrapper +export class RouteV2 + extends V2RouteSDK + implements IRoute +{ + public readonly protocol: Protocol = Protocol.V2 + public readonly path: Token[] + + constructor(v2Route: V2RouteSDK) { + super(v2Route.pools, v2Route.input, v2Route.output) + this.path = v2Route.tokenPath + } +} + +// UniV3 route wrapper +export class RouteUniV2 + extends V2RouteSDK + implements IRoute +{ + public readonly protocol: Protocol = Protocol.UNIV3 + public readonly path: Token[] + + constructor(v2Route: V2RouteSDK) { + super(v2Route.pools, v2Route.input, v2Route.output) + this.path = v2Route.tokenPath + } +} + +// Mixed route wrapper +export class MixedRoute + extends MixedRouteSDK + implements IRoute +{ + public readonly protocol: Protocol = Protocol.MIXED + + constructor(mixedRoute: MixedRouteSDK) { + super(mixedRoute.pools, mixedRoute.input, mixedRoute.output) + } +} \ No newline at end of file diff --git a/src/entities/TradeWrappers.ts b/src/entities/TradeWrappers.ts new file mode 100644 index 0000000..61f5569 --- /dev/null +++ b/src/entities/TradeWrappers.ts @@ -0,0 +1,404 @@ +import { CurrencyAmount, Fraction, Percent, Price, TradeType, wrappedCurrency } from '../index' +import { Pair, Route as V1RouteSDK, Trade as V1TradeSDK } from '../index' +import { Pool, V2Route as V2RouteSDK, V2Trade as V2TradeSDK } from '../index' +import invariant from 'tiny-invariant' +import { ONE, ZERO } from '../constants' +import { MixedRouteSDK } from './mixedRoute/route' +import { MixedRouteTrade as MixedRouteTradeSDK } from './mixedRoute/trade' +import { IRoute, MixedRoute, RouteV1, RouteV2, RouteUniV2 } from './RouteWrappers' + +export class MixedTrade { + public readonly routes: IRoute[] + public readonly tradeType: TradeType + private _outputAmount: CurrencyAmount | undefined + private _inputAmount: CurrencyAmount | undefined + + /** + * The swaps of the trade, i.e. which routes and how much is swapped in each that + * make up the trade. May consist of swaps in v1 or v2. + */ + public readonly swaps: { + route: IRoute + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + + // construct a trade across v1 and v2 routes from pre-computed amounts + public constructor({ + v1Routes, + v2Routes, + uniV3Routes, + tradeType, + mixedRoutes, + }: { + v1Routes: { + routev1: V1RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + v2Routes: { + routev2: V2RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + uniV3Routes: { + routeUniV3: V2RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + tradeType: TradeType + mixedRoutes?: { + mixedRoute: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + }) { + this.swaps = [] + this.routes = [] + // wrap v1 routes + for (const { routev1, inputAmount, outputAmount } of v1Routes) { + const route = new RouteV1(routev1) + this.routes.push(route) + this.swaps.push({ + route, + inputAmount, + outputAmount, + }) + } + // wrap v2 routes + for (const { routev2, inputAmount, outputAmount } of v2Routes) { + const route = new RouteV2(routev2) + this.routes.push(route) + this.swaps.push({ + route, + inputAmount, + outputAmount, + }) + } + // wrap univ3 routes + for (const { routeUniV3, inputAmount, outputAmount } of uniV3Routes) { + const route = new RouteUniV2(routeUniV3) + this.routes.push(route) + this.swaps.push({ + route, + inputAmount, + outputAmount, + }) + } + // wrap mixedRoutes + if (mixedRoutes) { + for (const { mixedRoute, inputAmount, outputAmount } of mixedRoutes) { + const route = new MixedRoute(mixedRoute) + this.routes.push(route) + this.swaps.push({ + route, + inputAmount, + outputAmount, + }) + } + } + this.tradeType = tradeType + + // each route must have the same input and output currency + const inputCurrency = this.swaps[0].inputAmount.currency + const outputCurrency = this.swaps[0].outputAmount.currency + invariant( + this.swaps.every(({ route }) => { + const chainId=route.pools[0].chainId + return wrappedCurrency(inputCurrency,chainId).equals(wrappedCurrency(route.input,chainId))}), + 'INPUT_CURRENCY_MATCH' + ) + invariant( + this.swaps.every(({ route }) => { + const chainId=route.pools[0].chainId + return wrappedCurrency(outputCurrency,chainId).equals(wrappedCurrency(route.output,chainId))}), + 'OUTPUT_CURRENCY_MATCH' + ) + + // pools must be unique inter protocols + const numPools = this.swaps.map(({ route }) => route.pools.length).reduce((total, cur) => total + cur, 0) + const poolAddressSet = new Set() + for (const { route } of this.swaps) { + for (const pool of route.pools) { + if (pool instanceof Pool) { + poolAddressSet.add(Pool.getAddress(pool.token0, pool.token1)) + } else if (pool instanceof Pair) { + const pair = pool + poolAddressSet.add(Pair.getAddress(pair.token0, pair.token1)) + } else { + throw new Error('Unexpected pool type in route when constructing trade object') + } + } + } + invariant(numPools == poolAddressSet.size, 'POOLS_DUPLICATED') + } + + public get inputAmount(): CurrencyAmount { + if (this._inputAmount) { + return this._inputAmount + } + + const inputCurrency = this.swaps[0].inputAmount.currency + const totalInputFromRoutes = this.swaps + .map(({ inputAmount }) => inputAmount) + .reduce((total, cur) => total.add(cur), new CurrencyAmount(inputCurrency, 0)) + + this._inputAmount = totalInputFromRoutes + return this._inputAmount + } + + public get outputAmount(): CurrencyAmount { + if (this._outputAmount) { + return this._outputAmount + } + + const outputCurrency = this.swaps[0].outputAmount.currency + const totalOutputFromRoutes = this.swaps + .map(({ outputAmount }) => outputAmount) + .reduce((total, cur) => total.add(cur), new CurrencyAmount(outputCurrency, 0)) + + this._outputAmount = totalOutputFromRoutes + return this._outputAmount + } + + private _executionPrice: Price | undefined + + /** + * The price expressed in terms of output amount/input amount. + */ + public get executionPrice(): Price { + return ( + this._executionPrice ?? + (this._executionPrice = new Price( + this.inputAmount.currency, + this.outputAmount.currency, + this.inputAmount.raw, + this.outputAmount.raw + )) + ) + } + + /** + * The cached result of the price impact computation + * @private + */ + private _priceImpact: Percent | undefined + /** + * Returns the percent difference between the route's mid price and the price impact + */ + public get priceImpact(): Percent { + if (this._priceImpact) { + return this._priceImpact + } + + let spotOutputAmount = new CurrencyAmount(this.outputAmount.currency, 0) + for (const { route, inputAmount } of this.swaps) { + const midPrice = route.midPrice + spotOutputAmount = spotOutputAmount.add(midPrice.quote(inputAmount)) + } + + const priceImpact = spotOutputAmount.subtract(this.outputAmount).divide(spotOutputAmount) + this._priceImpact = new Percent(priceImpact.numerator, priceImpact.denominator) + + return this._priceImpact + } + + /** + * Get the minimum amount that must be received from this trade for the given slippage tolerance + * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade + * @returns The amount out + */ + public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount { + invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE') + if (this.tradeType === TradeType.EXACT_OUTPUT) { + return amountOut + } else { + const slippageAdjustedAmountOut = new Fraction(ONE) + .add(slippageTolerance) + .invert() + .multiply(amountOut.raw).quotient + return new CurrencyAmount(amountOut.currency, slippageAdjustedAmountOut) + } + } + + /** + * Get the maximum amount in that can be spent via this trade for the given slippage tolerance + * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade + * @returns The amount in + */ + public maximumAmountIn(slippageTolerance: Percent, amountIn = this.inputAmount): CurrencyAmount { + invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE') + if (this.tradeType === TradeType.EXACT_INPUT) { + return amountIn + } else { + const slippageAdjustedAmountIn = new Fraction(ONE).add(slippageTolerance).multiply(amountIn.raw).quotient + return new CurrencyAmount(amountIn.currency, slippageAdjustedAmountIn) + } + } + + /** + * Return the execution price after accounting for slippage tolerance + * @param slippageTolerance the allowed tolerated slippage + * @returns The execution price + */ + public worstExecutionPrice(slippageTolerance: Percent): Price { + return new Price( + this.inputAmount.currency, + this.outputAmount.currency, + this.maximumAmountIn(slippageTolerance).raw, + this.minimumAmountOut(slippageTolerance).raw + ) + } + + public static async fromRoutes( + v1Routes: { + routev1: V1RouteSDK + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount + }[], + v2Routes: { + routev2: V2RouteSDK + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount + }[], + uniV3Routes: { + routeUniV3: V2RouteSDK + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount + }[], + tradeType: TradeType, + mixedRoutes?: { + mixedRoute: MixedRouteSDK + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount + }[] + ): Promise { + const populatedV1Routes: { + routev1: V1RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + const populatedV2Routes: { + routev2: V2RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + const populatedUniV3Routes: { + routeUniV3: V2RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + const populatedMixedRoutes: { + mixedRoute: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + for (const { routev1, amount } of v1Routes) { + const v1Trade = new V1TradeSDK(routev1, amount, tradeType) + const { inputAmount, outputAmount } = v1Trade + + populatedV1Routes.push({ + routev1, + inputAmount, + outputAmount, + }) + } + + for (const { routev2, amount } of v2Routes) { + const v2Trade = await V2TradeSDK.fromRoute(routev2, amount, tradeType) + const { inputAmount, outputAmount } = v2Trade + + populatedV2Routes.push({ + routev2, + inputAmount, + outputAmount, + }) + } + + for (const { routeUniV3, amount } of uniV3Routes) { + const v2Trade = await V2TradeSDK.fromRoute(routeUniV3, amount, tradeType) + const { inputAmount, outputAmount } = v2Trade + + populatedUniV3Routes.push({ + routeUniV3, + inputAmount, + outputAmount, + }) + } + + if (mixedRoutes) { + for (const { mixedRoute, amount } of mixedRoutes) { + const mixedRouteTrade = await MixedRouteTradeSDK.fromRoute(mixedRoute, amount, tradeType) + const { inputAmount, outputAmount } = mixedRouteTrade + + populatedMixedRoutes.push({ + mixedRoute, + inputAmount, + outputAmount, + }) + } + } + + return new MixedTrade({ + v1Routes: populatedV1Routes, + v2Routes: populatedV2Routes, + uniV3Routes: populatedUniV3Routes, + mixedRoutes: populatedMixedRoutes, + tradeType, + }) + } + + public static async fromRoute( + route: V1RouteSDK | V2RouteSDK | MixedRouteSDK, + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount, + tradeType: TradeType + ): Promise { + let v1Routes: { + routev1: V1RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + let v2Routes: { + routev2: V2RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + let uniV3Routes: { + routeUniV3: V2RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + let mixedRoutes: { + mixedRoute: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + if (route instanceof V1RouteSDK) { + const v1Trade = new V1TradeSDK(route, amount, tradeType) + const { inputAmount, outputAmount } = v1Trade + v1Routes = [{ routev1: route, inputAmount, outputAmount }] + } else if (route instanceof V2RouteSDK) { + const v2Trade = await V2TradeSDK.fromRoute(route, amount, tradeType) + const { inputAmount, outputAmount } = v2Trade + v2Routes = [{ routev2: route, inputAmount, outputAmount }] + } else if (route instanceof MixedRouteSDK) { + const mixedRouteTrade = await MixedRouteTradeSDK.fromRoute(route, amount, tradeType) + const { inputAmount, outputAmount } = mixedRouteTrade + mixedRoutes = [{ mixedRoute: route, inputAmount, outputAmount }] + } else { + throw new Error('Invalid route type') + } + + return new MixedTrade({ + v1Routes, + v2Routes, + uniV3Routes, + mixedRoutes, + tradeType, + }) + } +} \ No newline at end of file diff --git a/src/entities/currency.ts b/src/entities/currency.ts index c2b1cde..5accd45 100644 --- a/src/entities/currency.ts +++ b/src/entities/currency.ts @@ -25,6 +25,7 @@ export class Currency { public static readonly BNB: Currency = new Currency(18, 'BNB', 'Binance Coin') public static readonly FTM: Currency = new Currency(18, 'FTM', 'Fantom') public static readonly AVAX: Currency = new Currency(18, 'AVAX', 'Avalanche') + public static readonly ROUTE: Currency = new Currency(18, 'ROUTE', 'ROUTE') public static readonly NATIVE = { [ChainId.MAINNET]: Currency.ETHER, @@ -39,8 +40,11 @@ export class Currency { [ChainId.BSC]: Currency.BNB, [ChainId.FANTOM]: Currency.FTM, [ChainId.HARMONY]: Currency.ONE, - [ChainId.AVALANCHE]: Currency.AVAX - + [ChainId.AVALANCHE]: Currency.AVAX, + [ChainId.BASE]: Currency.ETHER, + [ChainId.OPTIMISM]: Currency.ETHER, + [ChainId.MANTLE]: Currency.ETHER, + [ChainId.ROUTER]: Currency.ROUTE } /** diff --git a/src/entities/fractions/price.ts b/src/entities/fractions/price.ts index 58b3288..b7b00f7 100644 --- a/src/entities/fractions/price.ts +++ b/src/entities/fractions/price.ts @@ -1,7 +1,5 @@ import { Token } from '../token' import { TokenAmount } from './tokenAmount' -import { currencyEquals } from '../token' -import invariant from 'tiny-invariant' import JSBI from 'jsbi' import { BigintIsh, Rounding, TEN } from '../../constants' @@ -52,14 +50,14 @@ export class Price extends Fraction { } public multiply(other: Price): Price { - invariant(currencyEquals(this.quoteCurrency, other.baseCurrency), 'TOKEN') + // invariant(currencyEquals(this.quoteCurrency, other.baseCurrency), 'TOKEN')// to be fixed const fraction = super.multiply(other) return new Price(this.baseCurrency, other.quoteCurrency, fraction.denominator, fraction.numerator) } // performs floor division on overflow public quote(currencyAmount: CurrencyAmount): CurrencyAmount { - invariant(currencyEquals(currencyAmount.currency, this.baseCurrency), 'TOKEN') + // invariant(currencyEquals(currencyAmount.currency, this.baseCurrency), 'TOKEN')// to be fixed if (this.quoteCurrency instanceof Token) { return new TokenAmount(this.quoteCurrency, super.multiply(currencyAmount.raw).quotient) } diff --git a/src/entities/index.ts b/src/entities/index.ts index c217ba5..7f17458 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -3,5 +3,17 @@ export * from './pair' export * from './route' export * from './trade' export * from './currency' +export * from './pool' +export * from './positions' +export * from './order' +export * from './tick' +export * from './tickDataProvider' +export * from './v2route' +export * from './v2trade' +export * from './protocol' +export * from './RouteWrappers' +export * from './TradeWrappers' +export * from './mixedRoute/route' +export * from './mixedRoute/trade' export * from './fractions' diff --git a/src/entities/mixedRoute/route.ts b/src/entities/mixedRoute/route.ts new file mode 100644 index 0000000..b90de4f --- /dev/null +++ b/src/entities/mixedRoute/route.ts @@ -0,0 +1,90 @@ +import invariant from 'tiny-invariant' + +import { Currency,Pair, Pool,Price, Token,wrappedCurrency } from '../index' + +type TPool = Pair | Pool + +/** + * Represents a list of pools or pairs through which a swap can occur + * @template TInput The input token + * @template TOutput The output token + */ +export class MixedRouteSDK { + public readonly pools: TPool[] + public readonly path: Token[] + public readonly input: Currency + public readonly output: Currency + + private _midPrice: Price | null = null + + /** + * Creates an instance of route. + * @param pools An array of `TPool` objects (pools or pairs), ordered by the route the swap will take + * @param input The input token + * @param output The output token + */ + public constructor(pools: TPool[], input: Currency, output: Currency) { + invariant(pools.length > 0, 'POOLS') + + const chainId = pools[0].chainId + const allOnSameChain = pools.every((pool) => pool.chainId === chainId) + invariant(allOnSameChain, 'CHAIN_IDS') + + const wrappedInput = wrappedCurrency(input,chainId) + invariant(pools[0].involvesToken(wrappedInput), 'INPUT') + + invariant(pools[pools.length - 1].involvesToken(wrappedCurrency(output,chainId)), 'OUTPUT') + + /** + * Normalizes token0-token1 order and selects the next token/fee step to add to the path + * */ + const tokenPath: Token[] = [wrappedInput] + for (const [i, pool] of pools.entries()) { + const currentInputToken = tokenPath[i] + invariant(currentInputToken.equals(pool.token0) || currentInputToken.equals(pool.token1), 'PATH') + const nextToken = currentInputToken.equals(pool.token0) ? pool.token1 : pool.token0 + tokenPath.push(nextToken) + } + + this.pools = pools + this.path = tokenPath + this.input = input + this.output = output ?? tokenPath[tokenPath.length - 1] + } + + public get chainId(): number { + return this.pools[0].chainId + } + + /** + * Returns the mid price of the route + */ + public get midPrice(): Price { + if (this._midPrice !== null) return this._midPrice + + const price = this.pools.slice(1).reduce( + ({ nextInput, price }, pool) => { + return nextInput.equals(pool.token0) + ? { + nextInput: pool.token1, + price: price.multiply(pool.token0Price), + } + : { + nextInput: pool.token0, + price: price.multiply(pool.token1Price), + } + }, + this.pools[0].token0.equals(wrappedCurrency(this.input,this.pools[0].chainId)) + ? { + nextInput: this.pools[0].token1, + price: this.pools[0].token0Price, + } + : { + nextInput: this.pools[0].token0, + price: this.pools[0].token1Price, + } + ).price + + return (this._midPrice = new Price(this.input, this.output, price.denominator, price.numerator)) + } +} \ No newline at end of file diff --git a/src/entities/mixedRoute/trade.ts b/src/entities/mixedRoute/trade.ts new file mode 100644 index 0000000..4124a06 --- /dev/null +++ b/src/entities/mixedRoute/trade.ts @@ -0,0 +1,496 @@ +import { Currency, Fraction, Percent, Price, CurrencyAmount, wrappedCurrency,wrappedAmount,TokenAmount,Pair,Pool,BestTradeOptions } from '../index' +import invariant from 'tiny-invariant' +import { ONE, ZERO,TradeType } from '../../constants' +import { MixedRouteSDK } from './route' +import { sortedInsert } from '../../index'; + +/** + * Trades comparator, an extension of the input output comparator that also considers other dimensions of the trade in ranking them + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @template TradeType The trade type, either exact input or exact output + * @param a The first trade to compare + * @param b The second trade to compare + * @returns A sorted ordering for two neighboring elements in a trade array + */ +export function mixedTradeComparator( + a: MixedRouteTrade, + b: MixedRouteTrade +) { + // must have same input and output token for comparison + const chainId= a.route.chainId; + invariant(wrappedCurrency(a.inputAmount.currency,chainId).equals(wrappedCurrency(b.inputAmount.currency,chainId)), 'INPUT_CURRENCY') + invariant(wrappedCurrency(a.outputAmount.currency,chainId).equals(wrappedCurrency(b.outputAmount.currency,chainId)), 'OUTPUT_CURRENCY') + if (a.outputAmount.equalTo(b.outputAmount)) { + if (a.inputAmount.equalTo(b.inputAmount)) { + // consider the number of hops since each hop costs gas + const aHops = a.swaps.reduce((total, cur) => total + cur.route.path.length, 0) + const bHops = b.swaps.reduce((total, cur) => total + cur.route.path.length, 0) + return aHops - bHops + } + // trade A requires less input than trade B, so A should come first + if (a.inputAmount.lessThan(b.inputAmount)) { + return -1 + } else { + return 1 + } + } else { + // tradeA has less output than trade B, so should come second + if (a.outputAmount.lessThan(b.outputAmount)) { + return 1 + } else { + return -1 + } + } +} + +/** + * Represents a trade executed against a set of routes where some percentage of the input is + * split across each route. + * + * Each route has its own set of pools. Pools can not be re-used across routes. + * + * Does not account for slippage, i.e., changes in price environment that can occur between + * the time the trade is submitted and when it is executed. + * @notice This class is functionally the same as the `Trade` class in the `@uniswap/v3-sdk` package, aside from typing and some input validation. + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @template TradeType The trade type, either exact input or exact output + */ +export class MixedRouteTrade { + /** + * @deprecated Deprecated in favor of 'swaps' property. If the trade consists of multiple routes + * this will return an error. + * + * When the trade consists of just a single route, this returns the route of the trade, + * i.e. which pools the trade goes through. + */ + public get route(): MixedRouteSDK { + invariant(this.swaps.length == 1, 'MULTIPLE_ROUTES') + return this.swaps[0].route + } + + /** + * The swaps of the trade, i.e. which routes and how much is swapped in each that + * make up the trade. + */ + public readonly swaps: { + route: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + + /** + * The type of the trade, either exact in or exact out. + */ + public readonly tradeType: TradeType + + /** + * The cached result of the input amount computation + * @private + */ + private _inputAmount: CurrencyAmount | undefined + + /** + * The input amount for the trade assuming no slippage. + */ + public get inputAmount(): CurrencyAmount { + if (this._inputAmount) { + return this._inputAmount + } + + const inputCurrency = this.swaps[0].inputAmount.currency + const totalInputFromRoutes = this.swaps + .map(({ inputAmount }) => inputAmount) + .reduce((total, cur) => total.add(cur), new CurrencyAmount(inputCurrency, 0)) + + this._inputAmount = totalInputFromRoutes + return this._inputAmount + } + + /** + * The cached result of the output amount computation + * @private + */ + private _outputAmount: CurrencyAmount | undefined + + /** + * The output amount for the trade assuming no slippage. + */ + public get outputAmount(): CurrencyAmount { + if (this._outputAmount) { + return this._outputAmount + } + + const outputCurrency = this.swaps[0].outputAmount.currency + const totalOutputFromRoutes = this.swaps + .map(({ outputAmount }) => outputAmount) + .reduce((total, cur) => total.add(cur), new CurrencyAmount(outputCurrency, 0)) + + this._outputAmount = totalOutputFromRoutes + return this._outputAmount + } + + /** + * The cached result of the computed execution price + * @private + */ + private _executionPrice: Price | undefined + + /** + * The price expressed in terms of output amount/input amount. + */ + public get executionPrice(): Price { + return ( + this._executionPrice ?? + (this._executionPrice = new Price( + this.inputAmount.currency, + this.outputAmount.currency, + this.inputAmount.raw, + this.outputAmount.raw + )) + ) + } + + /** + * The cached result of the price impact computation + * @private + */ + private _priceImpact: Percent | undefined + + /** + * Returns the percent difference between the route's mid price and the price impact + */ + public get priceImpact(): Percent { + if (this._priceImpact) { + return this._priceImpact + } + + let spotOutputAmount = new CurrencyAmount(this.outputAmount.currency, 0) + for (const { route, inputAmount } of this.swaps) { + const midPrice = route.midPrice + spotOutputAmount = spotOutputAmount.add(midPrice.quote(inputAmount)) + } + + const priceImpact = spotOutputAmount.subtract(this.outputAmount).divide(spotOutputAmount) + this._priceImpact = new Percent(priceImpact.numerator, priceImpact.denominator) + + return this._priceImpact + } + + /** + * Constructs a trade by simulating swaps through the given route + * @template TInput The input token, either Ether or an ERC-20. + * @template TOutput The output token, either Ether or an ERC-20. + * @template TradeType The type of the trade, either exact in or exact out. + * @param route route to swap through + * @param amount the amount specified, either input or output, depending on tradeType + * @param tradeType whether the trade is an exact input or exact output swap + * @returns The route + */ + public static async fromRoute( + route: MixedRouteSDK, + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount, + tradeType: TradeType + ): Promise { + const amounts: TokenAmount[] = new Array(route.path.length) + let inputAmount: CurrencyAmount + let outputAmount: CurrencyAmount + const chainId=route.chainId + + invariant(tradeType === TradeType.EXACT_INPUT, 'TRADE_TYPE') + + invariant(wrappedCurrency(amount.currency,chainId).equals(wrappedCurrency(route.input,chainId)), 'INPUT') + amounts[0] = wrappedAmount(amount,chainId) + for (let i = 0; i < route.path.length - 1; i++) { + const pool = route.pools[i] + const [outputAmount] = await pool.getOutputAmount(amounts[i]) + amounts[i + 1] = outputAmount + } + inputAmount = new CurrencyAmount(route.input, amount.raw) + outputAmount =new CurrencyAmount( + route.output, + amounts[amounts.length - 1].raw + ) + + return new MixedRouteTrade({ + routes: [{ inputAmount, outputAmount, route }], + tradeType, + }) + } + + /** + * Constructs a trade from routes by simulating swaps + * + * @template TInput The input token, either Ether or an ERC-20. + * @template TOutput The output token, either Ether or an ERC-20. + * @template TradeType The type of the trade, either exact in or exact out. + * @param routes the routes to swap through and how much of the amount should be routed through each + * @param tradeType whether the trade is an exact input or exact output swap + * @returns The trade + */ + public static async fromRoutes( + routes: { + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount + route: MixedRouteSDK + }[], + tradeType: TradeType + ): Promise { + const populatedRoutes: { + route: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + invariant(tradeType === TradeType.EXACT_INPUT, 'TRADE_TYPE') + + for (const { route, amount } of routes) { + const amounts: TokenAmount[] = new Array(route.path.length) + let inputAmount: CurrencyAmount + let outputAmount: CurrencyAmount + const chainId=route.chainId + + invariant(wrappedCurrency(amount.currency,chainId).equals(wrappedCurrency(route.input,chainId)), 'INPUT') + inputAmount = new CurrencyAmount(route.input, amount.raw) + amounts[0] = new TokenAmount(wrappedCurrency(route.input,chainId), amount.raw) + + for (let i = 0; i < route.path.length - 1; i++) { + const pool = route.pools[i] + const [outputAmount] = await pool.getOutputAmount(amounts[i]) + amounts[i + 1] = outputAmount + } + + outputAmount = new CurrencyAmount( + route.output, + amounts[amounts.length - 1].raw + ) + + populatedRoutes.push({ route, inputAmount, outputAmount }) + } + + return new MixedRouteTrade({ + routes: populatedRoutes, + tradeType, + }) + } + + /** + * Creates a trade without computing the result of swapping through the route. Useful when you have simulated the trade + * elsewhere and do not have any tick data + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @template TradeType The type of the trade, either exact in or exact out + * @param constructorArguments The arguments passed to the trade constructor + * @returns The unchecked trade + */ + public static createUncheckedTrade(constructorArguments: { + route: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + tradeType: TradeType + }): MixedRouteTrade { + return new MixedRouteTrade({ + ...constructorArguments, + routes: [ + { + inputAmount: constructorArguments.inputAmount, + outputAmount: constructorArguments.outputAmount, + route: constructorArguments.route, + }, + ], + }) + } + + /** + * Creates a trade without computing the result of swapping through the routes. Useful when you have simulated the trade + * elsewhere and do not have any tick data + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @template TradeType The type of the trade, either exact in or exact out + * @param constructorArguments The arguments passed to the trade constructor + * @returns The unchecked trade + */ + public static createUncheckedTradeWithMultipleRoutes(constructorArguments: { + routes: { + route: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + tradeType: TradeType + }): MixedRouteTrade { + return new MixedRouteTrade(constructorArguments) + } + + /** + * Construct a trade by passing in the pre-computed property values + * @param routes The routes through which the trade occurs + * @param tradeType The type of trade, exact input or exact output + */ + private constructor({ + routes, + tradeType, + }: { + routes: { + route: MixedRouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + tradeType: TradeType + }) { + const inputCurrency = routes[0].inputAmount.currency + const outputCurrency = routes[0].outputAmount.currency + invariant( + routes.every(({ route }) =>{ + const chainId=route.chainId + return wrappedCurrency(inputCurrency,chainId).equals(wrappedCurrency(route.input,chainId))}), + 'INPUT_CURRENCY_MATCH' + ) + invariant( + routes.every(({ route }) =>{ + const chainId=route.chainId + return wrappedCurrency(outputCurrency,chainId).equals(wrappedCurrency(route.output,chainId))}), + 'OUTPUT_CURRENCY_MATCH' + ) + + const numPools = routes.map(({ route }) => route.pools.length).reduce((total, cur) => total + cur, 0) + const poolAddressSet = new Set() + for (const { route } of routes) { + for (const pool of route.pools) { + pool instanceof Pool + ? poolAddressSet.add(Pool.getAddress(pool.token0, pool.token1)) + : poolAddressSet.add(Pair.getAddress(pool.token0, pool.token1)) + } + } + + invariant(numPools == poolAddressSet.size, 'POOLS_DUPLICATED') + + invariant(tradeType === TradeType.EXACT_INPUT, 'TRADE_TYPE') + + this.swaps = routes + this.tradeType = tradeType + } + + /** + * Get the minimum amount that must be received from this trade for the given slippage tolerance + * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade + * @returns The amount out + */ + public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount { + invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE') + /// does not support exactOutput, as enforced in the constructor + const slippageAdjustedAmountOut = new Fraction(ONE) + .add(slippageTolerance) + .invert() + .multiply(amountOut.raw).quotient + return new CurrencyAmount(amountOut.currency, slippageAdjustedAmountOut) + } + + /** + * Get the maximum amount in that can be spent via this trade for the given slippage tolerance + * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade + * @returns The amount in + */ + public maximumAmountIn(slippageTolerance: Percent, amountIn = this.inputAmount): CurrencyAmount { + invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE') + return amountIn + /// does not support exactOutput + } + + /** + * Return the execution price after accounting for slippage tolerance + * @param slippageTolerance the allowed tolerated slippage + * @returns The execution price + */ + public worstExecutionPrice(slippageTolerance: Percent): Price { + return new Price( + this.inputAmount.currency, + this.outputAmount.currency, + this.maximumAmountIn(slippageTolerance).raw, + this.minimumAmountOut(slippageTolerance).raw + ) + } + + /** + * Given a list of pools, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token + * amount to an output token, making at most `maxHops` hops. + * Note this does not consider aggregation, as routes are linear. It's possible a better route exists by splitting + * the amount in among multiple routes. + * @param pools the pools to consider in finding the best trade + * @param nextAmountIn exact amount of input currency to spend + * @param currencyOut the desired currency out + * @param maxNumResults maximum number of results to return + * @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pool + * @param currentPools used in recursion; the current list of pools + * @param currencyAmountIn used in recursion; the original value of the currencyAmountIn parameter + * @param bestTrades used in recursion; the current list of best trades + * @returns The exact in trade + */ + public static async bestTradeExactIn( + pools: (Pool | Pair)[], + currencyAmountIn: CurrencyAmount, + currencyOut: Currency, + { maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {}, + // used in recursion. + currentPools: (Pool | Pair)[] = [], + nextAmountIn: CurrencyAmount = currencyAmountIn, + bestTrades: MixedRouteTrade[] = [] + ): Promise { + invariant(pools.length > 0, 'POOLS') + invariant(maxHops > 0, 'MAX_HOPS') + invariant(currencyAmountIn === nextAmountIn || currentPools.length > 0, 'INVALID_RECURSION') + const chainId=pools[0].chainId + const amountIn = wrappedAmount(nextAmountIn,chainId) + const tokenOut = wrappedCurrency(currencyOut,chainId) + for (let i = 0; i < pools.length; i++) { + const pool = pools[i] + // pool irrelevant + if (!pool.token0.equals(amountIn.token) && !pool.token1.equals(amountIn.token)) continue + if (pool instanceof Pair) { + if ((pool as Pair).reserve0.equalTo(ZERO) || (pool as Pair).reserve1.equalTo(ZERO)) continue + } + + let amountOut: TokenAmount + try { + ;[amountOut] = await pool.getOutputAmount(amountIn) + } catch (error) { + // input too low + // @ts-ignore[2571] error is unknown + if (error.isInsufficientInputAmountError) { + continue + } + throw error + } + // we have arrived at the output token, so this is the final trade of one of the paths + if (amountOut.token && amountOut.token.equals(tokenOut)) { + sortedInsert( + bestTrades, + await MixedRouteTrade.fromRoute( + new MixedRouteSDK([...currentPools, pool], currencyAmountIn.currency, currencyOut), + currencyAmountIn, + TradeType.EXACT_INPUT + ), + maxNumResults, + mixedTradeComparator + ) + } else if (maxHops > 1 && pools.length > 1) { + const poolsExcludingThisPool = pools.slice(0, i).concat(pools.slice(i + 1, pools.length)) + + // otherwise, consider all the other paths that lead from this token as long as we have not exceeded maxHops + await MixedRouteTrade.bestTradeExactIn( + poolsExcludingThisPool, + currencyAmountIn, + currencyOut, + { + maxNumResults, + maxHops: maxHops - 1, + }, + [...currentPools, pool], + amountOut, + bestTrades + ) + } + } + + return bestTrades + } +} \ No newline at end of file diff --git a/src/entities/order.ts b/src/entities/order.ts new file mode 100644 index 0000000..bc236b3 --- /dev/null +++ b/src/entities/order.ts @@ -0,0 +1,194 @@ +import JSBI from "jsbi" +import { Pool } from "./pool" +import invariant from "tiny-invariant" +import { BigintIsh, Price, TokenAmount,FullMath, TickMath,TradeType, tickToPrice } from '../index' +import { Q96 } from "../internalConstants" + +interface OrderConstructorArgs { + pool: Pool + tick: number + amount: BigintIsh + zeroForOne: boolean + tradeType: TradeType +} + +enum ORDER_TYPE { + BUY_ORDER, + SELL_ORDER +} +/** + * Represents a limit order on a Dfyn V2 Pool + */ + export class Order { + public readonly pool: Pool + public readonly tick: number + public readonly amount: JSBI + public readonly zeroForOne: boolean + /** + * The type of the trade, either exact in or exact out. + */ + public readonly tradeType: TradeType + + // cached resuts for the getters + private _tokenAmountIn: TokenAmount | null = null + private _tokenAmountOut: TokenAmount | null = null + + /** + * Constructs a position for a given pool with the given liquidity + * @param pool For which pool the liquidity is assigned + * @param tick The tick where limit order sits + * @param amountIn The amount of liquidity that is in the position + * @param tickUpper The upper tick of the position + */ + public constructor({ pool, tick, amount, zeroForOne,tradeType }:OrderConstructorArgs){ + invariant(tick >= TickMath.MIN_TICK && tick % pool.tickSpacing === 0, 'INVALID_TICK') + invariant(tick <= TickMath.MAX_TICK && tick % pool.tickSpacing === 0, 'INVALID_TICK') + // const sqrtpriceX96=TickMath.getSqrtRatioAtTick(tick) + // if (zeroForOne) { + // invariant( + // JSBI.greaterThan(sqrtpriceX96,pool.sqrtRatioX96) + // , "INVALID_SELL_ORDER"); + // } else { + // invariant(JSBI.lessThan(sqrtpriceX96,pool.sqrtRatioX96), "INVALID_BUY_ORDER"); + // } + this.pool = pool + this.tick = tick + this.zeroForOne = zeroForOne + this.amount = JSBI.BigInt(amount) + this.tradeType = tradeType + } + + /** + * Returns the price of where limit order sits + */ + public get atPrice(): Price { + return tickToPrice(this.pool.token0, this.pool.token1, this.tick) + } + + /** + * Returns the type of limit order + */ + public get type(): ORDER_TYPE { + if (this.zeroForOne) { + return ORDER_TYPE.BUY_ORDER + } else { + return ORDER_TYPE.SELL_ORDER + } + } + + // if exactIn return new TokenAmount( this.pool.token0, this.amountIn.toString() ) + // else + // if + // zeroForOne = false + // let amountOut = FullMath.mulDivRoundingUp(this.amountIn, this.sqrtpriceX96, Q96); + // amountOut = FullMath.mulDivRoundingUp(amountOut, this.sqrtpriceX96, Q96); + // this._expectedTokenAmountOut = new TokenAmount( + // this.pool.token1, + // amountOut.toString() + // ) + // else + // let amountOut = FullMath.mulDivRoundingUp(this.amountIn, Q96, + // this.sqrtpriceX96); + // amountOut = FullMath.mulDivRoundingUp(amountOut, Q96, + // this.sqrtpriceX96); + // this._expectedTokenAmountOut = new TokenAmount( + // this.pool.token0, + // amountOut.toString() + // ) + // + + + + /** + * Returns the amountIn of limit order + */ + public get tokenAmountIn(): TokenAmount { + if (this._tokenAmountIn === null) { + if(this.tradeType===TradeType.EXACT_INPUT) + { + if (!this.zeroForOne) { + this._tokenAmountIn = new TokenAmount( + this.pool.token1, + this.amount.toString() + ) + } else { + this._tokenAmountIn = new TokenAmount( + this.pool.token0, + this.amount.toString() + ) + } + } else { + if (!this.zeroForOne) {// 1 -> 0 + let amountIn = FullMath.mulDivRoundingUp(this.amount, this.sqrtpriceX96, Q96); + amountIn = FullMath.mulDivRoundingUp(amountIn, this.sqrtpriceX96, Q96); + this._tokenAmountIn = new TokenAmount( + this.pool.token1, + amountIn.toString() + ) + } else {// 0 -> 1 + let amountIn = FullMath.mulDivRoundingUp(this.amount, Q96, + this.sqrtpriceX96); + amountIn = FullMath.mulDivRoundingUp(amountIn, Q96, + this.sqrtpriceX96); + this._tokenAmountIn = new TokenAmount( + this.pool.token0, + amountIn.toString() + ) + } + } + } + return this._tokenAmountIn + } + + /** + * Returns the expected amountOut of limit order + */ + public get tokenAmountOut(): TokenAmount { + if (this._tokenAmountOut === null) { + if(this.tradeType===TradeType.EXACT_OUTPUT) + { + if (this.zeroForOne) { + this._tokenAmountOut = new TokenAmount( + this.pool.token1, + this.amount.toString() + ) + } else { + this._tokenAmountOut = new TokenAmount( + this.pool.token0, + this.amount.toString() + ) + } + } else { + if (this.zeroForOne) {// 0->1 + let amountOut = FullMath.mulDivRoundingUp(this.amount, this.sqrtpriceX96, Q96); + amountOut = FullMath.mulDivRoundingUp(amountOut, this.sqrtpriceX96, Q96); + this._tokenAmountOut = new TokenAmount( + this.pool.token1, + amountOut.toString() + ) + } else {// 1->0 + let amountOut = FullMath.mulDivRoundingUp(this.amount, Q96, + this.sqrtpriceX96); + amountOut = FullMath.mulDivRoundingUp(amountOut, Q96, + this.sqrtpriceX96); + this._tokenAmountOut = new TokenAmount( + this.pool.token0, + amountOut.toString() + ) + } + } + } + return this._tokenAmountOut + } + /** + * Returns the expected amountIn of limit order + */ + + /** + * Returns the sqrtpriceX96 + */ + public get sqrtpriceX96():JSBI{ + return TickMath.getSqrtRatioAtTick(this.tick) + } + +} \ No newline at end of file diff --git a/src/entities/pool.ts b/src/entities/pool.ts new file mode 100644 index 0000000..e12732b --- /dev/null +++ b/src/entities/pool.ts @@ -0,0 +1,331 @@ +import { TokenAmount, Price, Token } from './index' +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { V2_DEPLOYER_ADDRESS, FeeAmount, TICK_SPACINGS,BigintIsh } from '../constants' +import { NEGATIVE_ONE, ONE, Q192, ZERO } from '../internalConstants' +import { computePoolAddress } from '../utils/computePoolAddress' +// import { LiquidityMath } from '../utils/liquidityMath' +// import { SwapMath } from '../utils/swapMath' +import { TickMath } from '../utils/tickMath' +import { Tick, TickConstructorArgs } from './tick' +import { NoTickDataProvider, TickDataProvider } from './tickDataProvider' +import { TickListDataProvider } from './tickListDataProvider' + +// interface StepComputations { +// sqrtPriceStartX96: JSBI +// tickNext: number +// initialized: boolean +// sqrtPriceNextX96: JSBI +// amountIn: JSBI +// amountOut: JSBI +// feeAmount: JSBI +// } + +/** + * By default, pools will not allow operations that require ticks. + */ +const NO_TICK_DATA_PROVIDER_DEFAULT = new NoTickDataProvider() + +/** + * Represents a V3 pool + */ +export class Pool { + public readonly token0: Token + public readonly token1: Token + public readonly fee: FeeAmount + public readonly sqrtRatioX96: JSBI + public readonly liquidity: JSBI + public readonly tickCurrent: number + public readonly tickDataProvider: TickDataProvider + + private _token0Price?: Price + private _token1Price?: Price + + + // dfyn: we are computing pool address using to factory(deployer address), + public static getAddress( + tokenA: Token, + tokenB: Token, + initCodeHashManualOverride?: string, + deployerAddressOverride?: string + ): string { + return computePoolAddress({ + deployerAddress: deployerAddressOverride ?? V2_DEPLOYER_ADDRESS[tokenA.chainId], + tokenA, + tokenB, + initCodeHashManualOverride + }) + } + + /** + * Construct a pool + * @param tokenA One of the tokens in the pool + * @param tokenB The other token in the pool + * @param fee The fee in hundredths of a bips of the input amount of every swap that is collected by the pool + * @param sqrtRatioX96 The sqrt of the current ratio of amounts of token1 to token0 + * @param liquidity The current value of in range liquidity + * @param tickCurrent The current tick of the pool + * @param ticks The current state of the pool ticks or a data provider that can return tick data + */ + public constructor( + tokenA: Token, + tokenB: Token, + fee: FeeAmount, + sqrtRatioX96: BigintIsh, + liquidity: BigintIsh, + tickCurrent: number, + ticks: TickDataProvider | (Tick | TickConstructorArgs)[] = NO_TICK_DATA_PROVIDER_DEFAULT + ) { + invariant(Number.isInteger(fee) && fee < 1_000_000, 'FEE') + + const tickCurrentSqrtRatioX96 = TickMath.getSqrtRatioAtTick(tickCurrent) + const nextTickSqrtRatioX96 = TickMath.getSqrtRatioAtTick(tickCurrent + 1) + invariant( + JSBI.greaterThanOrEqual(JSBI.BigInt(sqrtRatioX96), tickCurrentSqrtRatioX96) && + JSBI.lessThanOrEqual(JSBI.BigInt(sqrtRatioX96), nextTickSqrtRatioX96), + 'PRICE_BOUNDS' + ) + // always create a copy of the list since we want the pool's tick list to be immutable + ;[this.token0, this.token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] + this.fee = fee + this.sqrtRatioX96 = JSBI.BigInt(sqrtRatioX96) + this.liquidity = JSBI.BigInt(liquidity) + this.tickCurrent = tickCurrent + this.tickDataProvider = Array.isArray(ticks) ? new TickListDataProvider(ticks, TICK_SPACINGS[fee]) : ticks + } + + /** + * Returns true if the token is either token0 or token1 + * @param token The token to check + * @returns True if token is either token0 or token + */ + public involvesToken(token: Token): boolean { + return token.equals(this.token0) || token.equals(this.token1) + } + + /** + * Returns the current mid price of the pool in terms of token0, i.e. the ratio of token1 over token0 + */ + public get token0Price(): Price { + return ( + this._token0Price ?? + (this._token0Price = new Price( + this.token0, + this.token1, + Q192, + JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96) + )) + ) + } + + /** + * Returns the current mid price of the pool in terms of token1, i.e. the ratio of token0 over token1 + */ + public get token1Price(): Price { + return ( + this._token1Price ?? + (this._token1Price = new Price( + this.token1, + this.token0, + JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96), + Q192 + )) + ) + } + + /** + * Return the price of the given token in terms of the other token in the pool. + * @param token The token to return price of + * @returns The price of the given token, in terms of the other. + */ + public priceOf(token: Token): Price { + invariant(this.involvesToken(token), 'TOKEN') + return token.equals(this.token0) ? this.token0Price : this.token1Price + } + + /** + * Returns the chain ID of the tokens in the pool. + */ + public get chainId(): number { + return this.token0.chainId + } + // // // // // // // // // // // // // // // // // // // // // // // // // // // + // // // // // // // // // Depends on swap function // // // // // // // // // + // // // // // // // // // // // // // // // // // // // // // // // // // // // + + /** + * Given an input amount of a token, return the computed output amount, and a pool with state updated after the trade + * @param inputAmount The input amount for which to quote the output amount + * @param sqrtPriceLimitX96 The Q64.96 sqrt price limit + * @returns The output amount and the pool with updated state + */ + public async getOutputAmount( + inputAmount: TokenAmount, + sqrtPriceLimitX96?: JSBI + ): Promise<[TokenAmount, Pool]> { + invariant(this.involvesToken(inputAmount.token), 'TOKEN') + + const zeroForOne = inputAmount.token.equals(this.token0) + + const { amountCalculated: outputAmount, sqrtRatioX96, liquidity, tickCurrent } = await this.swap( + zeroForOne, + inputAmount.raw, + sqrtPriceLimitX96 + ) + const outputToken = zeroForOne ? this.token1 : this.token0 + return [ + new TokenAmount(outputToken,JSBI.multiply(outputAmount, NEGATIVE_ONE)), + // TokenAmount.fromRawAmount(outputToken, JSBI.multiply(outputAmount, NEGATIVE_ONE)), + new Pool(this.token0, this.token1, this.fee, sqrtRatioX96, liquidity, tickCurrent, this.tickDataProvider) + ] + } + // // // // // // // // // // // // // // // // // // // // // // // // // // // + // // // // // // // // // Depends on swap function // // // // // // // // // + // // // // // // // // // // // // // // // // // // // // // // // // // // // + + /** + * Given a desired output amount of a token, return the computed input amount and a pool with state updated after the trade + * @param outputAmount the output amount for which to quote the input amount + * @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap + * @returns The input amount and the pool with updated state + */ + public async getInputAmount( + outputAmount: TokenAmount, + sqrtPriceLimitX96?: JSBI + ): Promise<[TokenAmount, Pool]> { + invariant(this.involvesToken(outputAmount.token), 'TOKEN') + + const zeroForOne = outputAmount.token.equals(this.token1) + + const { amountCalculated: inputAmount, sqrtRatioX96, liquidity, tickCurrent } = await this.swap( + zeroForOne, + JSBI.multiply(outputAmount.raw, NEGATIVE_ONE), + sqrtPriceLimitX96 + ) + const inputToken = zeroForOne ? this.token0 : this.token1 + return [ + new TokenAmount(inputToken,inputAmount), + new Pool(this.token0, this.token1, this.fee, sqrtRatioX96, liquidity, tickCurrent, this.tickDataProvider) + ] + } + + + // // // // // // // // // // // // // // // // // // // // // // // // // // // + // // // // // Swap Logic needs to be modified how we are doing it// // // // // + // // // // // // // // // // // // // // // // // // // // // // // // // // // + /** + * Executes a swap + * @param zeroForOne Whether the amount in is token0 or token1 + * @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + * @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap + * @returns amountCalculated + * @returns sqrtRatioX96 + * @returns liquidity + * @returns tickCurrent + */ + private async swap( + zeroForOne: boolean, + amountSpecified: JSBI, + sqrtPriceLimitX96?: JSBI + ): Promise<{ amountCalculated: JSBI; sqrtRatioX96: JSBI; liquidity: JSBI; tickCurrent: number }> { + if (!sqrtPriceLimitX96) + sqrtPriceLimitX96 = zeroForOne + ? JSBI.add(TickMath.MIN_SQRT_RATIO, ONE) + : JSBI.subtract(TickMath.MAX_SQRT_RATIO, ONE) + + if (zeroForOne) { + invariant(JSBI.greaterThan(sqrtPriceLimitX96, TickMath.MIN_SQRT_RATIO), 'RATIO_MIN') + invariant(JSBI.lessThan(sqrtPriceLimitX96, this.sqrtRatioX96), 'RATIO_CURRENT') + } else { + invariant(JSBI.lessThan(sqrtPriceLimitX96, TickMath.MAX_SQRT_RATIO), 'RATIO_MAX') + invariant(JSBI.greaterThan(sqrtPriceLimitX96, this.sqrtRatioX96), 'RATIO_CURRENT') + } + + // const exactInput = JSBI.greaterThanOrEqual(amountSpecified, ZERO) + + // keep track of swap state + + const state = { + amountSpecifiedRemaining: amountSpecified, + amountCalculated: ZERO, + sqrtPriceX96: this.sqrtRatioX96, + tick: this.tickCurrent, + liquidity: this.liquidity + } + + // start swap while loop + // while (JSBI.notEqual(state.amountSpecifiedRemaining, ZERO) && state.sqrtPriceX96 != sqrtPriceLimitX96) { + // let step: Partial = {} + // step.sqrtPriceStartX96 = state.sqrtPriceX96 + + // // because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract) + // // by simply traversing to the next available tick, we instead need to exactly replicate + // // tickBitmap.nextInitializedTickWithinOneWord + // ;[step.tickNext, step.initialized] = await this.tickDataProvider.nextInitializedTickWithinOneWord( + // state.tick, + // zeroForOne, + // this.tickSpacing + // ) + + // if (step.tickNext < TickMath.MIN_TICK) { + // step.tickNext = TickMath.MIN_TICK + // } else if (step.tickNext > TickMath.MAX_TICK) { + // step.tickNext = TickMath.MAX_TICK + // } + + // step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext) + // ;[state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount] = SwapMath.computeSwapStep( + // state.sqrtPriceX96, + // (zeroForOne + // ? JSBI.lessThan(step.sqrtPriceNextX96, sqrtPriceLimitX96) + // : JSBI.greaterThan(step.sqrtPriceNextX96, sqrtPriceLimitX96)) + // ? sqrtPriceLimitX96 + // : step.sqrtPriceNextX96, + // state.liquidity, + // state.amountSpecifiedRemaining, + // this.fee + // ) + + // if (exactInput) { + // state.amountSpecifiedRemaining = JSBI.subtract( + // state.amountSpecifiedRemaining, + // JSBI.add(step.amountIn, step.feeAmount) + // ) + // state.amountCalculated = JSBI.subtract(state.amountCalculated, step.amountOut) + // } else { + // state.amountSpecifiedRemaining = JSBI.add(state.amountSpecifiedRemaining, step.amountOut) + // state.amountCalculated = JSBI.add(state.amountCalculated, JSBI.add(step.amountIn, step.feeAmount)) + // } + + // // TODO + // if (JSBI.equal(state.sqrtPriceX96, step.sqrtPriceNextX96)) { + // // if the tick is initialized, run the tick transition + // if (step.initialized) { + // let liquidityNet = JSBI.BigInt((await this.tickDataProvider.getTick(step.tickNext)).liquidityNet) + // // if we're moving leftward, we interpret liquidityNet as the opposite sign + // // safe because liquidityNet cannot be type(int128).min + // if (zeroForOne) liquidityNet = JSBI.multiply(liquidityNet, NEGATIVE_ONE) + + // state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet) + // } + + // state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext + // } else if (JSBI.notEqual(state.sqrtPriceX96, step.sqrtPriceStartX96)) { + // // updated comparison function + // // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + // state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96) + // } + // } + + return { + amountCalculated: state.amountCalculated, + sqrtRatioX96: state.sqrtPriceX96, + liquidity: state.liquidity, + tickCurrent: state.tick + } + } + + public get tickSpacing(): number { + return TICK_SPACINGS[this.fee] + } +} diff --git a/src/entities/positions.ts b/src/entities/positions.ts new file mode 100644 index 0000000..7a1ebfa --- /dev/null +++ b/src/entities/positions.ts @@ -0,0 +1,395 @@ +import { BigintIsh, Percent, Price, CurrencyAmount } from '../index' +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { ZERO } from '../internalConstants' +import { maxLiquidityForAmounts } from '../utils/maxLiquidityForAmounts' +import { tickToPrice } from '../utils/priceTickConversions' +import { SqrtPriceMath } from '../utils/sqrtPriceMath' +import { TickMath } from '../utils/tickMath' +import { encodeSqrtRatioX96 } from '../utils/encodeSqrtRatioX96' +import { Pool } from './pool' +import { MaxUint256 } from '../constants' + +interface PositionConstructorArgs { + pool: Pool + tickLower: number + tickUpper: number + liquidity: BigintIsh +} + +/** + * Represents a position on a Dfyn V2 Pool + */ +export class Position { + public readonly pool: Pool + public readonly tickLower: number + public readonly tickUpper: number + public readonly liquidity: JSBI + + // cached resuts for the getters + private _token0Amount: CurrencyAmount | null = null + private _token1Amount: CurrencyAmount | null = null + private _mintAmounts: Readonly<{ amount0: JSBI; amount1: JSBI }> | null = null + + /** + * Constructs a position for a given pool with the given liquidity + * @param pool For which pool the liquidity is assigned + * @param liquidity The amount of liquidity that is in the position + * @param tickLower The lower tick of the position + * @param tickUpper The upper tick of the position + */ + public constructor({ pool, liquidity, tickLower, tickUpper }: PositionConstructorArgs) { + invariant(tickLower < tickUpper, 'TICK_ORDER') + invariant(tickLower >= TickMath.MIN_TICK && tickLower % pool.tickSpacing === 0, 'TICK_LOWER') + invariant(tickUpper <= TickMath.MAX_TICK && tickUpper % pool.tickSpacing === 0, 'TICK_UPPER') + invariant((tickLower % pool.tickSpacing )%2=== 0, 'TICK_LOWER_ODD') + invariant((tickUpper % pool.tickSpacing )%2=== 0, 'TICK_UPPER_EVEN') + + this.pool = pool + this.tickLower = tickLower + this.tickUpper = tickUpper + this.liquidity = JSBI.BigInt(liquidity) + } + + /** + * Returns the price of token0 at the lower tick + */ + public get token0PriceLower(): Price { + return tickToPrice(this.pool.token0, this.pool.token1, this.tickLower) + } + + /** + * Returns the price of token0 at the upper tick + */ + public get token0PriceUpper(): Price { + return tickToPrice(this.pool.token0, this.pool.token1, this.tickUpper) + } + + /** + * Returns the amount of token0 that this position's liquidity could be burned for at the current pool price + */ + public get amount0(): CurrencyAmount { + if (this._token0Amount === null) { + if (this.pool.tickCurrent < this.tickLower) { + this._token0Amount = new CurrencyAmount( + this.pool.token0, + SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(this.tickLower), + TickMath.getSqrtRatioAtTick(this.tickUpper), + this.liquidity, + false + ) + ) + } else if (this.pool.tickCurrent < this.tickUpper) { + this._token0Amount = new CurrencyAmount( + this.pool.token0, + SqrtPriceMath.getAmount0Delta( + this.pool.sqrtRatioX96, + TickMath.getSqrtRatioAtTick(this.tickUpper), + this.liquidity, + false + ) + ) + } else { + this._token0Amount = new CurrencyAmount(this.pool.token0, ZERO) + } + } + return this._token0Amount + } + + /** + * Returns the amount of token1 that this position's liquidity could be burned for at the current pool price + */ + public get amount1(): CurrencyAmount { + if (this._token1Amount === null) { + if (this.pool.tickCurrent < this.tickLower) { + this._token1Amount = new CurrencyAmount(this.pool.token1, ZERO) + } else if (this.pool.tickCurrent < this.tickUpper) { + this._token1Amount = new CurrencyAmount( + this.pool.token1, + SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(this.tickLower), + this.pool.sqrtRatioX96, + this.liquidity, + false + ) + ) + } else { + this._token1Amount = new CurrencyAmount( + this.pool.token1, + SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(this.tickLower), + TickMath.getSqrtRatioAtTick(this.tickUpper), + this.liquidity, + false + ) + ) + } + } + return this._token1Amount + } + + /** + * Returns the lower and upper sqrt ratios if the price 'slips' up to slippage tolerance percentage + * @param slippageTolerance The amount by which the price can 'slip' before the transaction will revert + * @returns The sqrt ratios after slippage + */ + private ratiosAfterSlippage(slippageTolerance: Percent): { sqrtRatioX96Lower: JSBI; sqrtRatioX96Upper: JSBI } { + const priceLower = this.pool.token0Price.raw.multiply(new Percent(1).subtract(slippageTolerance)) + const priceUpper = this.pool.token0Price.raw.multiply(slippageTolerance.add(1)) + let sqrtRatioX96Lower = encodeSqrtRatioX96(priceLower.numerator, priceLower.denominator) + if (JSBI.lessThanOrEqual(sqrtRatioX96Lower, TickMath.MIN_SQRT_RATIO)) { + sqrtRatioX96Lower = JSBI.add(TickMath.MIN_SQRT_RATIO, JSBI.BigInt(1)) + } + let sqrtRatioX96Upper = encodeSqrtRatioX96(priceUpper.numerator, priceUpper.denominator) + if (JSBI.greaterThanOrEqual(sqrtRatioX96Upper, TickMath.MAX_SQRT_RATIO)) { + sqrtRatioX96Upper = JSBI.subtract(TickMath.MAX_SQRT_RATIO, JSBI.BigInt(1)) + } + return { + sqrtRatioX96Lower, + sqrtRatioX96Upper + } + } + + /** + * Returns the minimum amounts that must be sent in order to safely mint the amount of liquidity held by the position + * with the given slippage tolerance + * @param slippageTolerance Tolerance of unfavorable slippage from the current price + * @returns The amounts, with slippage + */ + public mintAmountsWithSlippage(slippageTolerance: Percent): Readonly<{ amount0: JSBI; amount1: JSBI }> { + // get lower/upper prices + const { sqrtRatioX96Upper, sqrtRatioX96Lower } = this.ratiosAfterSlippage(slippageTolerance) + + // construct counterfactual pools + const poolLower = new Pool( + this.pool.token0, + this.pool.token1, + this.pool.fee, + sqrtRatioX96Lower, + 0 /* liquidity doesn't matter */, + TickMath.getTickAtSqrtRatio(sqrtRatioX96Lower) + ) + const poolUpper = new Pool( + this.pool.token0, + this.pool.token1, + this.pool.fee, + sqrtRatioX96Upper, + 0 /* liquidity doesn't matter */, + TickMath.getTickAtSqrtRatio(sqrtRatioX96Upper) + ) + + // because the router is imprecise, we need to calculate the position that will be created (assuming no slippage) + const positionThatWillBeCreated = Position.fromAmounts({ + pool: this.pool, + tickLower: this.tickLower, + tickUpper: this.tickUpper, + ...this.mintAmounts, // the mint amounts are what will be passed as calldata + useFullPrecision: false + }) + + // we want the smaller amounts... + // ...which occurs at the upper price for amount0... + const { amount0 } = new Position({ + pool: poolUpper, + liquidity: positionThatWillBeCreated.liquidity, + tickLower: this.tickLower, + tickUpper: this.tickUpper + }).mintAmounts + // ...and the lower for amount1 + const { amount1 } = new Position({ + pool: poolLower, + liquidity: positionThatWillBeCreated.liquidity, + tickLower: this.tickLower, + tickUpper: this.tickUpper + }).mintAmounts + + return { amount0, amount1 } + } + + /** + * Returns the minimum amounts that should be requested in order to safely burn the amount of liquidity held by the + * position with the given slippage tolerance + * @param slippageTolerance tolerance of unfavorable slippage from the current price + * @returns The amounts, with slippage + */ + public burnAmountsWithSlippage(slippageTolerance: Percent): Readonly<{ amount0: JSBI; amount1: JSBI }> { + // get lower/upper prices + const { sqrtRatioX96Upper, sqrtRatioX96Lower } = this.ratiosAfterSlippage(slippageTolerance) + + // construct counterfactual pools + const poolLower = new Pool( + this.pool.token0, + this.pool.token1, + this.pool.fee, + sqrtRatioX96Lower, + 0 /* liquidity doesn't matter */, + TickMath.getTickAtSqrtRatio(sqrtRatioX96Lower) + ) + const poolUpper = new Pool( + this.pool.token0, + this.pool.token1, + this.pool.fee, + sqrtRatioX96Upper, + 0 /* liquidity doesn't matter */, + TickMath.getTickAtSqrtRatio(sqrtRatioX96Upper) + ) + + // we want the smaller amounts... + // ...which occurs at the upper price for amount0... + const amount0 = new Position({ + pool: poolUpper, + liquidity: this.liquidity, + tickLower: this.tickLower, + tickUpper: this.tickUpper + }).amount0 + // ...and the lower for amount1 + const amount1 = new Position({ + pool: poolLower, + liquidity: this.liquidity, + tickLower: this.tickLower, + tickUpper: this.tickUpper + }).amount1 + + return { amount0: amount0.raw, amount1: amount1.raw } + } + + /** + * Returns the minimum amounts that must be sent in order to mint the amount of liquidity held by the position at + * the current price for the pool + */ + public get mintAmounts(): Readonly<{ amount0: JSBI; amount1: JSBI }> { + if (this._mintAmounts === null) { + if (this.pool.tickCurrent < this.tickLower) { + return { + amount0: SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(this.tickLower), + TickMath.getSqrtRatioAtTick(this.tickUpper), + this.liquidity, + true + ), + amount1: ZERO + } + } else if (this.pool.tickCurrent < this.tickUpper) { + return { + amount0: SqrtPriceMath.getAmount0Delta( + this.pool.sqrtRatioX96, + TickMath.getSqrtRatioAtTick(this.tickUpper), + this.liquidity, + true + ), + amount1: SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(this.tickLower), + this.pool.sqrtRatioX96, + this.liquidity, + true + ) + } + } else { + return { + amount0: ZERO, + amount1: SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(this.tickLower), + TickMath.getSqrtRatioAtTick(this.tickUpper), + this.liquidity, + true + ) + } + } + } + return this._mintAmounts + } + + /** + * Computes the maximum amount of liquidity received for a given amount of token0, token1, + * and the prices at the tick boundaries. + * @param pool The pool for which the position should be created + * @param tickLower The lower tick of the position + * @param tickUpper The upper tick of the position + * @param amount0 token0 amount + * @param amount1 token1 amount + * @param useFullPrecision If false, liquidity will be maximized according to what the router can calculate, + * not what core can theoretically support + * @returns The amount of liquidity for the position + */ + public static fromAmounts({ + pool, + tickLower, + tickUpper, + amount0, + amount1, + useFullPrecision + }: { + pool: Pool + tickLower: number + tickUpper: number + amount0: BigintIsh + amount1: BigintIsh + useFullPrecision: boolean + }) { + const sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower) + const sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper) + return new Position({ + pool, + tickLower, + tickUpper, + liquidity: maxLiquidityForAmounts( + pool.sqrtRatioX96, + sqrtRatioAX96, + sqrtRatioBX96, + amount0, + amount1, + useFullPrecision + ) + }) + } + + /** + * Computes a position with the maximum amount of liquidity received for a given amount of token0, assuming an unlimited amount of token1 + * @param pool The pool for which the position is created + * @param tickLower The lower tick + * @param tickUpper The upper tick + * @param amount0 The desired amount of token0 + * @param useFullPrecision If true, liquidity will be maximized according to what the router can calculate, + * not what core can theoretically support + * @returns The position + */ + public static fromAmount0({ + pool, + tickLower, + tickUpper, + amount0, + useFullPrecision + }: { + pool: Pool + tickLower: number + tickUpper: number + amount0: BigintIsh + useFullPrecision: boolean + }) { + return Position.fromAmounts({ pool, tickLower, tickUpper, amount0, amount1: MaxUint256, useFullPrecision }) + } + + /** + * Computes a position with the maximum amount of liquidity received for a given amount of token1, assuming an unlimited amount of token0 + * @param pool The pool for which the position is created + * @param tickLower The lower tick + * @param tickUpper The upper tick + * @param amount1 The desired amount of token1 + * @returns The position + */ + public static fromAmount1({ + pool, + tickLower, + tickUpper, + amount1 + }: { + pool: Pool + tickLower: number + tickUpper: number + amount1: BigintIsh + }) { + // this function always uses full precision, + return Position.fromAmounts({ pool, tickLower, tickUpper, amount0: MaxUint256, amount1, useFullPrecision: true }) + } +} diff --git a/src/entities/protocol.ts b/src/entities/protocol.ts new file mode 100644 index 0000000..4a7063a --- /dev/null +++ b/src/entities/protocol.ts @@ -0,0 +1,6 @@ +export enum Protocol { + V1 = 'V1', + V2 = 'V2', + MIXED = 'MIXED', + UNIV3 = 'UNIV3', + } \ No newline at end of file diff --git a/src/entities/tick.ts b/src/entities/tick.ts new file mode 100644 index 0000000..860b6e0 --- /dev/null +++ b/src/entities/tick.ts @@ -0,0 +1,23 @@ +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { BigintIsh } from '../constants' +import { TickMath } from '../utils/tickMath' + +export interface TickConstructorArgs { + index: number + liquidityGross: BigintIsh + liquidityNet: BigintIsh +} + +export class Tick { + public readonly index: number + public readonly liquidityGross: JSBI + public readonly liquidityNet: JSBI + + constructor({ index, liquidityGross, liquidityNet }: TickConstructorArgs) { + invariant(index >= TickMath.MIN_TICK && index <= TickMath.MAX_TICK, 'TICK') + this.index = index + this.liquidityGross = JSBI.BigInt(liquidityGross) + this.liquidityNet = JSBI.BigInt(liquidityNet) + } +} diff --git a/src/entities/tickDataProvider.ts b/src/entities/tickDataProvider.ts new file mode 100644 index 0000000..5dd4c18 --- /dev/null +++ b/src/entities/tickDataProvider.ts @@ -0,0 +1,39 @@ +import { BigintIsh } from '../constants' + +/** + * Provides information about ticks + */ +export interface TickDataProvider { + /** + * Return information corresponding to a specific tick + * @param tick the tick to load + */ + getTick(tick: number): Promise<{ liquidityNet: BigintIsh }> + + /** + * Return the next tick that is initialized within a single word + * @param tick The current tick + * @param lte Whether the next tick should be lte the current tick + * @param tickSpacing The tick spacing of the pool + */ + nextInitializedTickWithinOneWord(tick: number, lte: boolean, tickSpacing: number): Promise<[number, boolean]> +} + +/** + * This tick data provider does not know how to fetch any tick data. It throws whenever it is required. Useful if you + * do not need to load tick data for your use case. + */ +export class NoTickDataProvider implements TickDataProvider { + private static ERROR_MESSAGE = 'No tick data provider was given' + async getTick(_tick: number): Promise<{ liquidityNet: BigintIsh }> { + throw new Error(NoTickDataProvider.ERROR_MESSAGE) + } + + async nextInitializedTickWithinOneWord( + _tick: number, + _lte: boolean, + _tickSpacing: number + ): Promise<[number, boolean]> { + throw new Error(NoTickDataProvider.ERROR_MESSAGE) + } +} diff --git a/src/entities/tickListDataProvider.ts b/src/entities/tickListDataProvider.ts new file mode 100644 index 0000000..f390f2b --- /dev/null +++ b/src/entities/tickListDataProvider.ts @@ -0,0 +1,25 @@ +import { BigintIsh } from '../constants' +import { TickList } from '../utils/tickList' +import { Tick, TickConstructorArgs } from './tick' +import { TickDataProvider } from './tickDataProvider' + +/** + * A data provider for ticks that is backed by an in-memory array of ticks. + */ +export class TickListDataProvider implements TickDataProvider { + private ticks: readonly Tick[] + + constructor(ticks: (Tick | TickConstructorArgs)[], tickSpacing: number) { + const ticksMapped: Tick[] = ticks.map(t => (t instanceof Tick ? t : new Tick(t))) + TickList.validateList(ticksMapped, tickSpacing) + this.ticks = ticksMapped + } + + async getTick(tick: number): Promise<{ liquidityNet: BigintIsh; liquidityGross: BigintIsh }> { + return TickList.getTick(this.ticks, tick) + } + + async nextInitializedTickWithinOneWord(tick: number, lte: boolean, tickSpacing: number): Promise<[number, boolean]> { + return TickList.nextInitializedTickWithinOneWord(this.ticks, tick, lte, tickSpacing) + } +} diff --git a/src/entities/token.ts b/src/entities/token.ts index 2398da9..3b1c429 100644 --- a/src/entities/token.ts +++ b/src/entities/token.ts @@ -82,7 +82,7 @@ export const WETH = { [ChainId.KOVAN]: new Token(ChainId.KOVAN, '0xd0A1E359811322d97991E03f863a0C30C2cF029C', 18, 'WETH', 'Wrapped Ether'), [ChainId.MATIC]: new Token( ChainId.MATIC, - '0x4c28f48448720e9000907bc2611f73022fdce1fa', + '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', 18, 'WMATIC', 'Wrapped Matic' @@ -101,20 +101,9 @@ export const WETH = { 'WETH', 'Wrapped ETH' ), - [ChainId.BSC]: new Token( - ChainId.BSC, - '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', - 18, - 'WBNB', - 'Wrapped BNB' - ), - [ChainId.FANTOM]: new Token( - ChainId.FANTOM, - '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', - 18, - 'WFTM', - 'Wrapped FTM' - ), + + [ChainId.BSC]: new Token(ChainId.BSC, '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 18, 'WBNB', 'Wrapped BNB'), + [ChainId.FANTOM]: new Token(ChainId.FANTOM, '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', 18, 'WFTM', 'Wrapped FTM'), [ChainId.HARMONY]: new Token( ChainId.HARMONY, '0xcF664087a5bB0237a0BAd6742852ec6c8d69A27a', @@ -122,19 +111,20 @@ export const WETH = { 'WONE', 'Wrapped ONE' ), - [ChainId.XDAI]: new Token( - ChainId.XDAI, - '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', - 18, - 'WXDAI', - 'Wrapped xDai' - ) - , + [ChainId.XDAI]: new Token(ChainId.XDAI, '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', 18, 'WXDAI', 'Wrapped xDai'), [ChainId.AVALANCHE]: new Token( ChainId.AVALANCHE, '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', 18, 'XAVAX', 'Wrapped AVAX' - ) + ), + // TODO: Base Chain + [ChainId.BASE]: new Token(ChainId.BASE, '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 18, 'WETH', 'Wrapped ETH'), + // TODO: Optimism Chain + [ChainId.OPTIMISM]: new Token(ChainId.BASE, '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 18, 'WETH', 'Wrapped ETH'), + // TODO: Mantle Chain + [ChainId.MANTLE]: new Token(ChainId.BASE, '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 18, 'WETH', 'Wrapped ETH'), + // TODO: Router Chain + [ChainId.ROUTER]: new Token(ChainId.ROUTER, '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 18, 'WETH', 'Wrapped ETH') } diff --git a/src/entities/trade.ts b/src/entities/trade.ts index a822407..2cfcbb9 100644 --- a/src/entities/trade.ts +++ b/src/entities/trade.ts @@ -87,13 +87,13 @@ export interface BestTradeOptions { * In other words, if the currency is NATIVE, returns the WETH token amount for the given chain. Otherwise, returns * the input currency amount. */ -function wrappedAmount(currencyAmount: CurrencyAmount, chainId: ChainId): TokenAmount { +export function wrappedAmount(currencyAmount: CurrencyAmount, chainId: ChainId): TokenAmount { if (currencyAmount instanceof TokenAmount) return currencyAmount if (!(currencyAmount.currency instanceof Token) && (currencyAmount.currency instanceof Currency)) return new TokenAmount(WETH[chainId], currencyAmount.raw) invariant(false, 'CURRENCY') } -function wrappedCurrency(currency: Currency, chainId: ChainId): Token { +export function wrappedCurrency(currency: Currency, chainId: ChainId): Token { if (currency instanceof Token) return currency if (!(currency instanceof Token) && (currency instanceof Currency)) return WETH[chainId] invariant(false, 'CURRENCY') diff --git a/src/entities/v2route.ts b/src/entities/v2route.ts new file mode 100644 index 0000000..69c8d5c --- /dev/null +++ b/src/entities/v2route.ts @@ -0,0 +1,84 @@ +import invariant from 'tiny-invariant' + +import { Currency, Price, Token, wrappedCurrency } from '../index' +import { Pool } from './pool' + +export class V2Route { + public readonly pools: Pool[] + public readonly tokenPath: Token[] + public readonly input: Currency + public readonly output: Currency + + private _midPrice: Price | null = null + + /** + * Creates an instance of route. + * @param pools An array of `Pool` objects, ordered by the route the swap will take + * @param input The input token + * @param output The output token + */ + public constructor(pools: Pool[], input: Currency, output: Currency) { + invariant(pools.length > 0, 'POOLS') + + const chainId = pools[0].chainId + const allOnSameChain = pools.every(pool => pool.chainId === chainId) + invariant(allOnSameChain, 'CHAIN_IDS') + + const wrappedInput = wrappedCurrency(input,chainId) + invariant(pools[0].involvesToken(wrappedInput), 'INPUT') + + invariant(pools[pools.length - 1].involvesToken(wrappedCurrency(output,chainId)), 'OUTPUT') + + /** + * Normalizes token0-token1 order and selects the next token/fee step to add to the path + * */ + const tokenPath: Token[] = [wrappedInput] + for (const [i, pool] of pools.entries()) { + const currentInputToken = tokenPath[i] + invariant(currentInputToken.equals(pool.token0) || currentInputToken.equals(pool.token1), 'PATH') + const nextToken = currentInputToken.equals(pool.token0) ? pool.token1 : pool.token0 + tokenPath.push(nextToken) + } + + this.pools = pools + this.tokenPath = tokenPath + this.input = input + this.output = output ?? tokenPath[tokenPath.length - 1] + } + + public get chainId(): number { + return this.pools[0].chainId + } + + /** + * Returns the mid price of the route + */ + public get midPrice(): Price { + if (this._midPrice !== null) return this._midPrice + + const price = this.pools.slice(1).reduce( + ({ nextInput, price }, pool) => { + return nextInput.equals(pool.token0) + ? { + nextInput: pool.token1, + price: price.multiply(pool.token0Price) + } + : { + nextInput: pool.token0, + price: price.multiply(pool.token1Price) + } + }, + this.pools[0].token0.equals(wrappedCurrency(this.input,this.pools[0].chainId)) + ? { + nextInput: this.pools[0].token1, + price: this.pools[0].token0Price + } + : { + nextInput: this.pools[0].token0, + price: this.pools[0].token1Price + } + ).price + + return (this._midPrice = new Price(this.input, this.output, price.denominator, price.numerator)) + } +} diff --git a/src/entities/v2trade.ts b/src/entities/v2trade.ts new file mode 100644 index 0000000..8157cba --- /dev/null +++ b/src/entities/v2trade.ts @@ -0,0 +1,613 @@ +import { ChainId, Fraction, Percent, Price, CurrencyAmount, TokenAmount,TradeType, wrappedCurrency, wrappedAmount } from '../index' +import invariant from 'tiny-invariant' +import { ONE, ZERO } from '../internalConstants' +import { Pool } from './pool' +import { V2Route } from './v2route' + + +export function v2TradeComparator( + a: V2Trade, + b: V2Trade, + chainId: ChainId +) { + // must have same input and output token for comparison + invariant(wrappedCurrency(a.inputAmount.currency,chainId).equals(wrappedCurrency(b.inputAmount.currency,chainId)), 'INPUT_CURRENCY') + invariant(wrappedCurrency(a.outputAmount.currency,chainId).equals(wrappedCurrency(b.outputAmount.currency,chainId)), 'OUTPUT_CURRENCY') + if (a.outputAmount.equalTo(b.outputAmount)) { + if (a.inputAmount.equalTo(b.inputAmount)) { + // consider the number of hops since each hop costs gas + const aHops = a.swaps.reduce((total, cur) => total + cur.route.tokenPath.length, 0) + const bHops = b.swaps.reduce((total, cur) => total + cur.route.tokenPath.length, 0) + return aHops - bHops + } + // trade A requires less input than trade B, so A should come first + if (a.inputAmount.lessThan(b.inputAmount)) { + return -1 + } else { + return 1 + } + } else { + // tradeA has less output than trade B, so should come second + if (a.outputAmount.lessThan(b.outputAmount)) { + return 1 + } else { + return -1 + } + } +} + +export interface BestV2TradeOptions { + // how many results to return + maxNumResults?: number + // the maximum number of hops a trade should contain + maxHops?: number +} + + +export class V2Trade { + /** + * @deprecated Deprecated in favor of 'swaps' property. If the trade consists of multiple routes + * this will return an error. + * + * When the trade consists of just a single route, this returns the route of the trade, + * i.e. which pools the trade goes through. + */ + public get route(): V2Route { + invariant(this.swaps.length == 1, 'MULTIPLE_ROUTES') + return this.swaps[0].route + } + + /** + * The swaps of the trade, i.e. which routes and how much is swapped in each that + * make up the trade. + */ + public readonly swaps: { + route: V2Route + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + + /** + * The type of the trade, either exact in or exact out. + */ + public readonly tradeType: TradeType + + /** + * The cached result of the input amount computation + * @private + */ + private _inputAmount: CurrencyAmount | undefined + + /** + * The input amount for the trade assuming no slippage. + */ + public get inputAmount(): CurrencyAmount { + if (this._inputAmount) { + return this._inputAmount + } + + const inputCurrency = this.swaps[0].inputAmount.currency + const totalInputFromRoutes = this.swaps + .map(({ inputAmount }) => inputAmount) + .reduce((total, cur) => total.add(cur), new CurrencyAmount(inputCurrency, 0)) + + this._inputAmount = totalInputFromRoutes + return this._inputAmount + } + + /** + * The cached result of the output amount computation + * @private + */ + private _outputAmount: CurrencyAmount | undefined + + /** + * The output amount for the trade assuming no slippage. + */ + public get outputAmount(): CurrencyAmount { + if (this._outputAmount) { + return this._outputAmount + } + + const outputCurrency = this.swaps[0].outputAmount.currency + const totalOutputFromRoutes = this.swaps + .map(({ outputAmount }) => outputAmount) + .reduce((total, cur) => total.add(cur), new CurrencyAmount(outputCurrency, 0)) + + this._outputAmount = totalOutputFromRoutes + return this._outputAmount + } + + /** + * The cached result of the computed execution price + * @private + */ + private _executionPrice: Price | undefined + + /** + * The price expressed in terms of output amount/input amount. + */ + public get executionPrice(): Price { + return ( + this._executionPrice ?? + (this._executionPrice = new Price( + this.inputAmount.currency, + this.outputAmount.currency, + this.inputAmount.raw, + this.outputAmount.raw + )) + ) + } + + /** + * The cached result of the price impact computation + * @private + */ + private _priceImpact: Percent | undefined + + /** + * Returns the percent difference between the route's mid price and the price impact + */ + public get priceImpact(): Percent { + if (this._priceImpact) { + return this._priceImpact + } + + let spotOutputAmount = new CurrencyAmount(this.outputAmount.currency, 0) + for (const { route, inputAmount } of this.swaps) { + const midPrice = route.midPrice + spotOutputAmount = spotOutputAmount.add(midPrice.quote(inputAmount)) + } + + const priceImpact = spotOutputAmount.subtract(this.outputAmount).divide(spotOutputAmount) + this._priceImpact = new Percent(priceImpact.numerator, priceImpact.denominator) + + return this._priceImpact + } + + /** + * Constructs an exact in trade with the given amount in and route + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @param route The route of the exact in trade + * @param amountIn The amount being passed in + * @returns The exact in trade + */ + public static async exactIn( + route: V2Route, + amountIn: CurrencyAmount + ): Promise { + return V2Trade.fromRoute(route, amountIn, TradeType.EXACT_INPUT) + } + + /** + * Constructs an exact out trade with the given amount out and route + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @param route The route of the exact out trade + * @param amountOut The amount returned by the trade + * @returns The exact out trade + */ + public static async exactOut( + route: V2Route, + amountOut: CurrencyAmount + ): Promise { + return V2Trade.fromRoute(route, amountOut, TradeType.EXACT_OUTPUT) + } + + /** + * Constructs a trade by simulating swaps through the given route + * @template TInput The input token, either Ether or an ERC-20. + * @template TOutput The output token, either Ether or an ERC-20. + * @template TradeType The type of the trade, either exact in or exact out. + * @param route route to swap through + * @param amount the amount specified, either input or output, depending on tradeType + * @param tradeType whether the trade is an exact input or exact output swap + * @returns The route + */ + public static async fromRoute( + route: V2Route, + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount: CurrencyAmount, + tradeType: TradeType + ): Promise { + const amounts: CurrencyAmount[] = new Array(route.tokenPath.length) + let inputAmount: CurrencyAmount + let outputAmount: CurrencyAmount + const chainId=route.chainId; + if (tradeType === TradeType.EXACT_INPUT) { + invariant(wrappedCurrency(amount.currency,chainId).equals(wrappedCurrency(route.input,chainId)), 'INPUT') + amounts[0] = wrappedAmount(amount,chainId) + for (let i = 0; i < route.tokenPath.length - 1; i++) { + const pool = route.pools[i] + const [outputAmount] = await pool.getOutputAmount(wrappedAmount(amounts[i],chainId)) + amounts[i + 1] = outputAmount + } + inputAmount = new CurrencyAmount(route.input, amount.raw) + outputAmount = new CurrencyAmount( + route.output, + amounts[amounts.length - 1].raw + ) + } else { + invariant(wrappedCurrency(amount.currency,chainId).equals(wrappedCurrency(route.output,chainId)), 'OUTPUT') + amounts[amounts.length - 1] = wrappedAmount(amount,chainId) + for (let i = route.tokenPath.length - 1; i > 0; i--) { + const pool = route.pools[i - 1] + const [inputAmount] = await pool.getInputAmount(wrappedAmount(amounts[i],chainId)) + amounts[i - 1] = inputAmount + } + inputAmount = new CurrencyAmount(route.input, amount.raw) + outputAmount = new CurrencyAmount(route.output, amount.raw) + } + + return new V2Trade({ + routes: [{ inputAmount, outputAmount, route }], + tradeType + }) + } + + /** + * Constructs a trade from routes by simulating swaps + * + * @template TInput The input token, either Ether or an ERC-20. + * @template TOutput The output token, either Ether or an ERC-20. + * @template TradeType The type of the trade, either exact in or exact out. + * @param routes the routes to swap through and how much of the amount should be routed through each + * @param tradeType whether the trade is an exact input or exact output swap + * @returns The trade + */ + public static async fromRoutes( + routes: { + amount: TradeType extends TradeType.EXACT_INPUT ? CurrencyAmount : CurrencyAmount + route: V2Route + }[], + tradeType: TradeType + ): Promise { + const populatedRoutes: { + route: V2Route + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] = [] + + for (const { route, amount } of routes) { + const amounts: CurrencyAmount[] = new Array(route.tokenPath.length) + let inputAmount: CurrencyAmount + let outputAmount: CurrencyAmount + const chainId=route.chainId; + if (tradeType === TradeType.EXACT_INPUT) { + invariant(wrappedCurrency(amount.currency,chainId).equals(wrappedCurrency(route.input,chainId)), 'INPUT') + inputAmount = new CurrencyAmount(route.input, amount.raw) + amounts[0] = new TokenAmount(wrappedCurrency(route.input,chainId), amount.raw) + + for (let i = 0; i < route.tokenPath.length - 1; i++) { + const pool = route.pools[i] + const [outputAmount] = await pool.getOutputAmount(wrappedAmount(amounts[i],chainId)) + amounts[i + 1] = outputAmount + } + + outputAmount = new CurrencyAmount( + route.output, + amounts[amounts.length - 1].raw + ) + } else { + invariant(wrappedCurrency(amount.currency,chainId).equals(wrappedCurrency(route.output,chainId)), 'OUTPUT') + outputAmount = new CurrencyAmount(route.output, amount.raw) + amounts[amounts.length - 1] =new CurrencyAmount( + wrappedCurrency(route.output,chainId), + amount.raw + ) + + for (let i = route.tokenPath.length - 1; i > 0; i--) { + const pool = route.pools[i - 1] + const [inputAmount] = await pool.getInputAmount(wrappedAmount(amounts[i],chainId)) + amounts[i - 1] = inputAmount + } + + inputAmount =new CurrencyAmount(route.input, amounts[0].raw) + } + + populatedRoutes.push({ route, inputAmount, outputAmount }) + } + + return new V2Trade({ + routes: populatedRoutes, + tradeType + }) + } + + /** + * Creates a trade without computing the result of swapping through the route. Useful when you have simulated the trade + * elsewhere and do not have any tick data + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @template TradeType The type of the trade, either exact in or exact out + * @param constructorArguments The arguments passed to the trade constructor + * @returns The unchecked trade + */ + public static createUncheckedTrade(constructorArguments: { + route: V2Route + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + tradeType: TradeType + }): V2Trade { + return new V2Trade({ + ...constructorArguments, + routes: [ + { + inputAmount: constructorArguments.inputAmount, + outputAmount: constructorArguments.outputAmount, + route: constructorArguments.route + } + ] + }) + } + + /** + * Creates a trade without computing the result of swapping through the routes. Useful when you have simulated the trade + * elsewhere and do not have any tick data + * @template TInput The input token, either Ether or an ERC-20 + * @template TOutput The output token, either Ether or an ERC-20 + * @template TradeType The type of the trade, either exact in or exact out + * @param constructorArguments The arguments passed to the trade constructor + * @returns The unchecked trade + */ + public static createUncheckedTradeWithMultipleRoutes(constructorArguments: { + routes: { + route: V2Route + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + tradeType: TradeType + }): V2Trade { + return new V2Trade(constructorArguments) + } + + /** + * Construct a trade by passing in the pre-computed property values + * @param routes The routes through which the trade occurs + * @param tradeType The type of trade, exact input or exact output + */ + private constructor({ + routes, + tradeType + }: { + routes: { + route: V2Route + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] + tradeType: TradeType + }) { + const chainId=routes[0].route.chainId + const inputCurrency = routes[0].inputAmount.currency + const outputCurrency = routes[0].outputAmount.currency + invariant( + routes.every(({ route }) => wrappedCurrency(inputCurrency,chainId).equals(wrappedCurrency(route.input,chainId))), + 'INPUT_CURRENCY_MATCH' + ) + invariant( + routes.every(({ route }) => wrappedCurrency(outputCurrency,chainId).equals(wrappedCurrency(route.output,chainId))), + 'OUTPUT_CURRENCY_MATCH' + ) + + const numPools = routes.map(({ route }) => route.pools.length).reduce((total, cur) => total + cur, 0) + const poolAddressSet = new Set() + for (const { route } of routes) { + for (const pool of route.pools) { + poolAddressSet.add(Pool.getAddress(pool.token0, pool.token1)) + } + } + + invariant(numPools == poolAddressSet.size, 'POOLS_DUPLICATED') + + this.swaps = routes + this.tradeType = tradeType + } + + /** + * Get the minimum amount that must be received from this trade for the given slippage tolerance + * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade + * @returns The amount out + */ + public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount { + invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE') + if (this.tradeType === TradeType.EXACT_OUTPUT) { + return amountOut + } else { + const slippageAdjustedAmountOut = new Fraction(ONE) + .add(slippageTolerance) + .invert() + .multiply(amountOut.raw).quotient + return new CurrencyAmount(amountOut.currency, slippageAdjustedAmountOut) + } + } + + /** + * Get the maximum amount in that can be spent via this trade for the given slippage tolerance + * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade + * @returns The amount in + */ + public maximumAmountIn(slippageTolerance: Percent, amountIn = this.inputAmount): CurrencyAmount { + invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE') + if (this.tradeType === TradeType.EXACT_INPUT) { + return amountIn + } else { + const slippageAdjustedAmountIn = new Fraction(ONE).add(slippageTolerance).multiply(amountIn.raw).quotient + return new CurrencyAmount(amountIn.currency, slippageAdjustedAmountIn) + } + } + + /** + * Return the execution price after accounting for slippage tolerance + * @param slippageTolerance the allowed tolerated slippage + * @returns The execution price + */ + public worstExecutionPrice(slippageTolerance: Percent): Price { + return new Price( + this.inputAmount.currency, + this.outputAmount.currency, + this.maximumAmountIn(slippageTolerance).raw, + this.minimumAmountOut(slippageTolerance).raw + ) + } + + /** + * Given a list of pools, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token + * amount to an output token, making at most `maxHops` hops. + * Note this does not consider aggregation, as routes are linear. It's possible a better route exists by splitting + * the amount in among multiple routes. + * @param pools the pools to consider in finding the best trade + * @param nextAmountIn exact amount of input currency to spend + * @param currencyOut the desired currency out + * @param maxNumResults maximum number of results to return + * @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pool + * @param currentPools used in recursion; the current list of pools + * @param currencyAmountIn used in recursion; the original value of the currencyAmountIn parameter + * @param bestTrades used in recursion; the current list of best trades + * @returns The exact in trade + */ +// public static async bestTradeExactIn( +// pools: Pool[], +// currencyAmountIn: CurrencyAmount, +// currencyOut: Currency, +// { maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {}, +// // used in recursion. +// currentPools: Pool[] = [], +// nextAmountIn: CurrencyAmount = currencyAmountIn, +// bestTrades: Trade[] = [] +// ): Promise { +// invariant(pools.length > 0, 'POOLS') +// invariant(maxHops > 0, 'MAX_HOPS') +// invariant(currencyAmountIn === nextAmountIn || currentPools.length > 0, 'INVALID_RECURSION') + +// const amountIn = nextAmountIn.wrapped +// const tokenOut = currencyOut.wrapped +// for (let i = 0; i < pools.length; i++) { +// const pool = pools[i] +// // pool irrelevant +// if (!pool.token0.equals(amountIn.currency) && !pool.token1.equals(amountIn.currency)) continue + +// let amountOut: CurrencyAmount +// try { +// ;[amountOut] = await pool.getOutputAmount(amountIn) +// } catch (error) { +// // input too low +// if (error.isInsufficientInputAmountError) { +// continue +// } +// throw error +// } +// // we have arrived at the output token, so this is the final trade of one of the paths +// if (amountOut.currency.isToken && amountOut.currency.equals(tokenOut)) { +// sortedInsert( +// bestTrades, +// await Trade.fromRoute( +// new Route([...currentPools, pool], currencyAmountIn.currency, currencyOut), +// currencyAmountIn, +// TradeType.EXACT_INPUT +// ), +// maxNumResults, +// tradeComparator +// ) +// } else if (maxHops > 1 && pools.length > 1) { +// const poolsExcludingThisPool = pools.slice(0, i).concat(pools.slice(i + 1, pools.length)) + +// // otherwise, consider all the other paths that lead from this token as long as we have not exceeded maxHops +// await Trade.bestTradeExactIn( +// poolsExcludingThisPool, +// currencyAmountIn, +// currencyOut, +// { +// maxNumResults, +// maxHops: maxHops - 1 +// }, +// [...currentPools, pool], +// amountOut, +// bestTrades +// ) +// } +// } + +// return bestTrades +// } + + /** + * similar to the above method but instead targets a fixed output amount + * given a list of pools, and a fixed amount out, returns the top `maxNumResults` trades that go from an input token + * to an output token amount, making at most `maxHops` hops + * note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting + * the amount in among multiple routes. + * @param pools the pools to consider in finding the best trade + * @param currencyIn the currency to spend + * @param currencyAmountOut the desired currency amount out + * @param nextAmountOut the exact amount of currency out + * @param maxNumResults maximum number of results to return + * @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pool + * @param currentPools used in recursion; the current list of pools + * @param bestTrades used in recursion; the current list of best trades + * @returns The exact out trade + */ +// public static async bestTradeExactOut( +// pools: Pool[], +// currencyIn: TInput, +// currencyAmountOut: CurrencyAmount, +// { maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {}, +// // used in recursion. +// currentPools: Pool[] = [], +// nextAmountOut: CurrencyAmount = currencyAmountOut, +// bestTrades: Trade[] = [] +// ): Promise[]> { +// invariant(pools.length > 0, 'POOLS') +// invariant(maxHops > 0, 'MAX_HOPS') +// invariant(currencyAmountOut === nextAmountOut || currentPools.length > 0, 'INVALID_RECURSION') + +// const amountOut = nextAmountOut.wrapped +// const tokenIn = currencyIn.wrapped +// for (let i = 0; i < pools.length; i++) { +// const pool = pools[i] +// // pool irrelevant +// if (!pool.token0.equals(amountOut.currency) && !pool.token1.equals(amountOut.currency)) continue + +// let amountIn: CurrencyAmount +// try { +// ;[amountIn] = await pool.getInputAmount(amountOut) +// } catch (error) { +// // not enough liquidity in this pool +// if (error.isInsufficientReservesError) { +// continue +// } +// throw error +// } +// // we have arrived at the input token, so this is the first trade of one of the paths +// if (amountIn.currency.equals(tokenIn)) { +// sortedInsert( +// bestTrades, +// await Trade.fromRoute( +// new Route([pool, ...currentPools], currencyIn, currencyAmountOut.currency), +// currencyAmountOut, +// TradeType.EXACT_OUTPUT +// ), +// maxNumResults, +// tradeComparator +// ) +// } else if (maxHops > 1 && pools.length > 1) { +// const poolsExcludingThisPool = pools.slice(0, i).concat(pools.slice(i + 1, pools.length)) + +// // otherwise, consider all the other paths that arrive at this token as long as we have not exceeded maxHops +// await Trade.bestTradeExactOut( +// poolsExcludingThisPool, +// currencyIn, +// currencyAmountOut, +// { +// maxNumResults, +// maxHops: maxHops - 1 +// }, +// [pool, ...currentPools], +// amountIn, +// bestTrades +// ) +// } +// } + +// return bestTrades +// } +} diff --git a/src/index.ts b/src/index.ts index f1d1d5a..dd6cae1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,10 +9,20 @@ export { FACTORY_ADDRESS, ROUTER_ADDRESS, INIT_CODE_HASH, - MINIMUM_LIQUIDITY + MINIMUM_LIQUIDITY, + V2_DEPLOYER_ADDRESS, + V2_MASTER_DEPLOYER_ADDRESS, + V2_FACTORY_ADDRESS, + V2_POOL_INIT_CODE_HASH, + FeeAmount, + TICK_SPACINGS } from './constants' export * from './errors' export * from './entities' export * from './router' export * from './fetcher' +export * from './utils' +export * from './MasterDeployer' +export * from './concentratedPoolManager' +export * from './limitOrderManager' diff --git a/src/internalConstants.ts b/src/internalConstants.ts new file mode 100644 index 0000000..97e6650 --- /dev/null +++ b/src/internalConstants.ts @@ -0,0 +1,10 @@ +import JSBI from 'jsbi' + +// constants used internally but not expected to be used externally +export const NEGATIVE_ONE = JSBI.BigInt(-1) +export const ZERO = JSBI.BigInt(0) +export const ONE = JSBI.BigInt(1) + +// used in liquidity amount math +export const Q96 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(96)) +export const Q192 = JSBI.exponentiate(Q96, JSBI.BigInt(2)) diff --git a/src/limitOrderManager.ts b/src/limitOrderManager.ts new file mode 100644 index 0000000..e41e01f --- /dev/null +++ b/src/limitOrderManager.ts @@ -0,0 +1,192 @@ +import { Interface } from "@ethersproject/abi"; +import { Order } from "entities/order"; +import { MethodParameters, Percent, Currency, Pool, toHex, wrappedCurrency } from "./index"; +import invariant from "tiny-invariant"; +import LimitOrderManagerAbi from "./abis/LimitOrderManager.json" +import JSBI from 'jsbi' +import { BigintIsh, ZERO } from "./constants"; +import { Multicall } from "./multicall"; + +/** + * Options for producing the calldata to create limit order. + */ +export interface CreateOrderOptions { + /** + * How much the pool price is allowed to move. + */ + slippageTolerance: Percent + + /** + * When the transaction expires, in epoch seconds. + */ + deadline: BigintIsh + + /** + * Whether to spend ether. If true, one of the pool tokens must be WETH, by default false + */ + useNative?: Currency + + /** + * The lower and upper old + */ + lowerOldTick: number + + /** + * The lower and upper old + */ + upperOldTick: number + + /** + * The lower and upper old + */ + recipient: string + +} + +export interface ClaimOrderOptions { + /** + * Indicates the ID of the position to collect for. + */ + tokenId: BigintIsh | BigintIsh[] + + /** + * When the transaction expires, in epoch seconds. + */ + deadline: BigintIsh + + unwrapVault: Boolean + +} +export interface CancelOrderOptions { + /** + * Indicates the ID of the position to collect for. + */ + tokenId: BigintIsh | BigintIsh[] + + /** + * When the transaction expires, in epoch seconds. + */ + deadline: BigintIsh + + unwrapVault: Boolean + +} + + + +export abstract class LimitOrderManager { + public static INTERFACE: Interface = new Interface(LimitOrderManagerAbi); + + /** + * Cannot be constructed. + */ + private constructor() { } + + public static createCallParameters(order: Order, options: CreateOrderOptions): MethodParameters { + invariant(JSBI.greaterThan(order.tokenAmountIn.raw, ZERO), 'ZERO_AMOUNT_IN') + + let calldata: string + + // get amounts + const { zeroForOne, tokenAmountIn, tick } = order; + const { lowerOldTick, upperOldTick } = options; + const amountIn = tokenAmountIn.raw; + const recipient = options.recipient; + + calldata = this.INTERFACE.encodeFunctionData('createLimitOrder', [ + Pool.getAddress(order.pool.token0, order.pool.token1), + tick, + lowerOldTick, + upperOldTick, + true, + amountIn.toString(), + zeroForOne, + recipient + ]) + + let value: string = toHex(0) + if (options.useNative) { + const wrapped = wrappedCurrency(options.useNative, order.tokenAmountIn.token.chainId) + invariant(order.tokenAmountIn.token.equals(wrapped), 'NO_WETH') + + const wrappedValue = amountIn.toString() + + value = toHex(wrappedValue) + } + + return { + calldata, + value + } + + } + + public static claimCallParameters(options: ClaimOrderOptions): MethodParameters { + const calldatas: string[] = [] + + const unwrapVault = options.unwrapVault + const tokenIdOrIds = options.tokenId + if (Array.isArray(tokenIdOrIds)) { + // If the tokenIdOrIds is an array of ids, iterate through each ids + tokenIdOrIds.forEach((tokenIdinBigInt) => { + // Handle each id here + const tokenId = toHex(tokenIdinBigInt) + calldatas.push(this.INTERFACE.encodeFunctionData('claimLimitOrder', [ + tokenId, + unwrapVault + ])) + }); + } else { + // If the tokenIdOrIds is a single tokenId, handle it directly + const tokenId = toHex(tokenIdOrIds) + calldatas.push(this.INTERFACE.encodeFunctionData('claimLimitOrder', [ + tokenId, + unwrapVault + ])) + } + + + const value = toHex(0) + + return { + calldata: Multicall.encodeMulticall(calldatas), + value + } + + } + + public static cancelCallParameters(options: CancelOrderOptions): MethodParameters { + let calldatas: string[] = [] + + const unwrapVault = options.unwrapVault + const tokenIdOrIds = options.tokenId + + if (Array.isArray(tokenIdOrIds)) { + // If the tokenIdOrIds is an array of ids, iterate through each ids + tokenIdOrIds.forEach((tokenIdinBigInt) => { + // Handle each id here + const tokenId = toHex(tokenIdinBigInt) + calldatas.push(this.INTERFACE.encodeFunctionData('cancelLimitOrder', [ + tokenId, + unwrapVault + ])) + }); + } else { + // If the tokenIdOrIds is a single tokenId, handle it directly + const tokenId = toHex(tokenIdOrIds) + calldatas.push(this.INTERFACE.encodeFunctionData('cancelLimitOrder', [ + tokenId, + unwrapVault + ])) + } + + const value = toHex(0) + + return { + calldata: Multicall.encodeMulticall(calldatas), + value + } + } + + +} \ No newline at end of file diff --git a/src/multicall.ts b/src/multicall.ts new file mode 100644 index 0000000..d70b54c --- /dev/null +++ b/src/multicall.ts @@ -0,0 +1,19 @@ +import { Interface } from '@ethersproject/abi' +import IMulticall from './abis/Multicall.json' + +export abstract class Multicall { + public static INTERFACE: Interface = new Interface(IMulticall) + + /** + * Cannot be constructed. + */ + private constructor() { } + + public static encodeMulticall(calldatas: string | string[]): string { + if (!Array.isArray(calldatas)) { + calldatas = [calldatas] + } + + return calldatas.length === 1 ? calldatas[0] : Multicall.INTERFACE.encodeFunctionData('multicall', [calldatas]) + } +} \ No newline at end of file diff --git a/src/utils/calldata.ts b/src/utils/calldata.ts new file mode 100644 index 0000000..d4c0302 --- /dev/null +++ b/src/utils/calldata.ts @@ -0,0 +1,30 @@ +import { BigintIsh } from '../constants' +import JSBI from 'jsbi' + +/** + * Generated method parameters for executing a call. + */ +export interface MethodParameters { + /** + * The hex encoded calldata to perform the given operation + */ + calldata: string + /** + * The amount of ether (wei) to send in hex. + */ + value: string +} + +/** + * Converts a big int to a hex string + * @param bigintIsh + * @returns The hex encoded calldata + */ +export function toHex(bigintIsh: BigintIsh) { + const bigInt = JSBI.BigInt(bigintIsh) + let hex = bigInt.toString(16) + if (hex.length % 2 !== 0) { + hex = `0${hex}` + } + return `0x${hex}` +} diff --git a/src/utils/computePoolAddress.ts b/src/utils/computePoolAddress.ts new file mode 100644 index 0000000..b0a0452 --- /dev/null +++ b/src/utils/computePoolAddress.ts @@ -0,0 +1,36 @@ +import { defaultAbiCoder } from '@ethersproject/abi' +import { getCreate2Address } from '@ethersproject/address' +import { keccak256 } from '@ethersproject/solidity' +import { Token } from '../index' +import { V2_POOL_INIT_CODE_HASH } from '../constants' + +/** + * Computes a pool address + * @param deployerAddress The Uniswap V3 factory address + * @param tokenA The first token of the pair, irrespective of sort order + * @param tokenB The second token of the pair, irrespective of sort order + * @param initCodeHashManualOverride Override the init code hash used to compute the pool address if necessary + * @returns The pool address + */ +export function computePoolAddress({ + deployerAddress, + tokenA, + tokenB, + initCodeHashManualOverride +}: { + deployerAddress: string + tokenA: Token + tokenB: Token + initCodeHashManualOverride?: string +}): string { + const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks + const salt=keccak256( + ['bytes'], + [defaultAbiCoder.encode(['address', 'address'], [token0.address, token1.address])] + ) + return getCreate2Address( + deployerAddress, + salt, + initCodeHashManualOverride ?? V2_POOL_INIT_CODE_HASH[token0.chainId] + ) +} diff --git a/src/utils/encodeSqrtRatioX96.ts b/src/utils/encodeSqrtRatioX96.ts new file mode 100644 index 0000000..c32d086 --- /dev/null +++ b/src/utils/encodeSqrtRatioX96.ts @@ -0,0 +1,17 @@ +import { BigintIsh } from '../constants' +import JSBI from 'jsbi' +import {sqrt} from "./index" + +/** + * Returns the sqrt ratio as a Q64.96 corresponding to a given ratio of amount1 and amount0 + * @param amount1 The numerator amount i.e., the amount of token1 + * @param amount0 The denominator amount i.e., the amount of token0 + * @returns The sqrt ratio + */ + +export function encodeSqrtRatioX96(amount1: BigintIsh, amount0: BigintIsh): JSBI { + const numerator = JSBI.leftShift(JSBI.BigInt(amount1), JSBI.BigInt(192)) + const denominator = JSBI.BigInt(amount0) + const ratioX192 = JSBI.divide(numerator, denominator) + return sqrt(ratioX192) +} diff --git a/src/utils/fullMath.ts b/src/utils/fullMath.ts new file mode 100644 index 0000000..97f1a36 --- /dev/null +++ b/src/utils/fullMath.ts @@ -0,0 +1,16 @@ +import JSBI from 'jsbi' +import { ONE, ZERO } from '../internalConstants' + +export abstract class FullMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static mulDivRoundingUp(a: JSBI, b: JSBI, denominator: JSBI): JSBI { + const product = JSBI.multiply(a, b) + let result = JSBI.divide(product, denominator) + if (JSBI.notEqual(JSBI.remainder(product, denominator), ZERO)) result = JSBI.add(result, ONE) + return result + } +} diff --git a/src/utils.ts b/src/utils/index.ts similarity index 84% rename from src/utils.ts rename to src/utils/index.ts index 867684d..4e6f4e8 100644 --- a/src/utils.ts +++ b/src/utils/index.ts @@ -3,7 +3,7 @@ import warning from 'tiny-warning' import JSBI from 'jsbi' import { getAddress } from '@ethersproject/address' -import { BigintIsh, ZERO, ONE, TWO, THREE, SolidityType, SOLIDITY_TYPE_MAXIMA } from './constants' +import { BigintIsh, ZERO, ONE, TWO, THREE, SolidityType, SOLIDITY_TYPE_MAXIMA } from '../constants' export function validateSolidityTypeInstance(value: JSBI, solidityType: SolidityType): void { invariant(JSBI.greaterThanOrEqual(value, ZERO), `${value} is not a ${solidityType}.`) @@ -25,7 +25,7 @@ export function parseBigintIsh(bigintIsh: BigintIsh): JSBI { return bigintIsh instanceof JSBI ? bigintIsh : typeof bigintIsh === 'bigint' - ? JSBI.BigInt(bigintIsh.toString()) + ? JSBI.BigInt(bigintIsh) : JSBI.BigInt(bigintIsh) } @@ -80,3 +80,16 @@ export function sortedInsert(items: T[], add: T, maxSize: number, comparator: return isFull ? items.pop()! : null } } + +export * from './computePoolAddress' +export * from './calldata' +export * from './isSorted' +export * from './mostSignificantBit' +export * from './tickList' +export * from './tickMath' +export * from './fullMath' +export * from './priceTickConversions' +export * from './encodeSqrtRatioX96' +export * from './maxLiquidityForAmounts' +export * from './sqrtPriceMath' +export * from './nearestUsableTick' diff --git a/src/utils/isSorted.ts b/src/utils/isSorted.ts new file mode 100644 index 0000000..763291c --- /dev/null +++ b/src/utils/isSorted.ts @@ -0,0 +1,15 @@ +/** + * Determines if a tick list is sorted + * @param list The tick list + * @param comparator The comparator + * @returns true if sorted + */ + export function isSorted(list: Array, comparator: (a: T, b: T) => number): boolean { + for (let i = 0; i < list.length - 1; i++) { + if (comparator(list[i], list[i + 1]) > 0) { + return false + } + } + return true + } + \ No newline at end of file diff --git a/src/utils/maxLiquidityForAmounts.ts b/src/utils/maxLiquidityForAmounts.ts new file mode 100644 index 0000000..9bef16d --- /dev/null +++ b/src/utils/maxLiquidityForAmounts.ts @@ -0,0 +1,91 @@ +import { BigintIsh } from '../constants' +import JSBI from 'jsbi' +import { Q96 } from '../internalConstants' + +/** + * Returns an imprecise maximum amount of liquidity received for a given amount of token 0. + * This function is available to accommodate LiquidityAmounts#getLiquidityForAmount0 in the v3 periphery, + * which could be more precise by at least 32 bits by dividing by Q64 instead of Q96 in the intermediate step, + * and shifting the subtracted ratio left by 32 bits. This imprecise calculation will likely be replaced in a future + * v3 router contract. + * @param sqrtRatioAX96 The price at the lower boundary + * @param sqrtRatioBX96 The price at the upper boundary + * @param amount0 The token0 amount + * @returns liquidity for amount0, imprecise + */ +function maxLiquidityForAmount0Imprecise(sqrtRatioAX96: JSBI, sqrtRatioBX96: JSBI, amount0: BigintIsh): JSBI { + if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] + } + const intermediate = JSBI.divide(JSBI.multiply(sqrtRatioAX96, sqrtRatioBX96), Q96) + return JSBI.divide(JSBI.multiply(JSBI.BigInt(amount0), intermediate), JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96)) +} + +/** + * Returns a precise maximum amount of liquidity received for a given amount of token 0 by dividing by Q64 instead of Q96 in the intermediate step, + * and shifting the subtracted ratio left by 32 bits. + * @param sqrtRatioAX96 The price at the lower boundary + * @param sqrtRatioBX96 The price at the upper boundary + * @param amount0 The token0 amount + * @returns liquidity for amount0, precise + */ +function maxLiquidityForAmount0Precise(sqrtRatioAX96: JSBI, sqrtRatioBX96: JSBI, amount0: BigintIsh): JSBI { + if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] + } + + const numerator = JSBI.multiply(JSBI.multiply(JSBI.BigInt(amount0), sqrtRatioAX96), sqrtRatioBX96) + const denominator = JSBI.multiply(Q96, JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96)) + + return JSBI.divide(numerator, denominator) +} + +/** + * Computes the maximum amount of liquidity received for a given amount of token1 + * @param sqrtRatioAX96 The price at the lower tick boundary + * @param sqrtRatioBX96 The price at the upper tick boundary + * @param amount1 The token1 amount + * @returns liquidity for amount1 + */ +function maxLiquidityForAmount1(sqrtRatioAX96: JSBI, sqrtRatioBX96: JSBI, amount1: BigintIsh): JSBI { + if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] + } + return JSBI.divide(JSBI.multiply(JSBI.BigInt(amount1), Q96), JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96)) +} + +/** + * Computes the maximum amount of liquidity received for a given amount of token0, token1, + * and the prices at the tick boundaries. + * @param sqrtRatioCurrentX96 the current price + * @param sqrtRatioAX96 price at lower boundary + * @param sqrtRatioBX96 price at upper boundary + * @param amount0 token0 amount + * @param amount1 token1 amount + * @param useFullPrecision if false, liquidity will be maximized according to what the router can calculate, + * not what core can theoretically support + */ +export function maxLiquidityForAmounts( + sqrtRatioCurrentX96: JSBI, + sqrtRatioAX96: JSBI, + sqrtRatioBX96: JSBI, + amount0: BigintIsh, + amount1: BigintIsh, + useFullPrecision: boolean +): JSBI { + if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] + } + + const maxLiquidityForAmount0 = useFullPrecision ? maxLiquidityForAmount0Precise : maxLiquidityForAmount0Imprecise + + if (JSBI.lessThanOrEqual(sqrtRatioCurrentX96, sqrtRatioAX96)) { + return maxLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0) + } else if (JSBI.lessThan(sqrtRatioCurrentX96, sqrtRatioBX96)) { + const liquidity0 = maxLiquidityForAmount0(sqrtRatioCurrentX96, sqrtRatioBX96, amount0) + const liquidity1 = maxLiquidityForAmount1(sqrtRatioAX96, sqrtRatioCurrentX96, amount1) + return JSBI.lessThan(liquidity0, liquidity1) ? liquidity0 : liquidity1 + } else { + return maxLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1) + } +} diff --git a/src/utils/mostSignificantBit.ts b/src/utils/mostSignificantBit.ts new file mode 100644 index 0000000..0aebe1c --- /dev/null +++ b/src/utils/mostSignificantBit.ts @@ -0,0 +1,24 @@ +import { MaxUint256 } from '../constants' +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { ZERO } from '../internalConstants' + +const TWO = JSBI.BigInt(2) +const POWERS_OF_2 = [128, 64, 32, 16, 8, 4, 2, 1].map((pow: number): [number, JSBI] => [ + pow, + JSBI.exponentiate(TWO, JSBI.BigInt(pow)) +]) + +export function mostSignificantBit(x: JSBI): number { + invariant(JSBI.greaterThan(x, ZERO), 'ZERO') + invariant(JSBI.lessThanOrEqual(x, MaxUint256), 'MAX') + + let msb: number = 0 + for (const [power, min] of POWERS_OF_2) { + if (JSBI.greaterThanOrEqual(x, min)) { + x = JSBI.signedRightShift(x, JSBI.BigInt(power)) + msb += power + } + } + return msb +} diff --git a/src/utils/nearestUsableTick.ts b/src/utils/nearestUsableTick.ts new file mode 100644 index 0000000..6c4642f --- /dev/null +++ b/src/utils/nearestUsableTick.ts @@ -0,0 +1,35 @@ +import invariant from 'tiny-invariant' +import { TickMath } from './tickMath' + +/** + * Returns the closest tick that is nearest a given tick and usable for the given tick spacing + * @param tick the target tick + * @param tickSpacing the spacing of the pool + */ +export function nearestUsableTick(tick: number, tickSpacing: number,even?:boolean) { + invariant(Number.isInteger(tick) && Number.isInteger(tickSpacing), 'INTEGERS') + invariant(tickSpacing > 0, 'TICK_SPACING') + invariant(tick >= TickMath.MIN_TICK && tick <= TickMath.MAX_TICK, 'TICK_BOUND') + const rounded = Math.round(tick / tickSpacing) * tickSpacing + const evenMultiple=Math.round(Math.round(tick / tickSpacing)/2)*2 + const roundedEven = (evenMultiple) * tickSpacing + const roundedOdd = (evenMultiple-1) * tickSpacing + if(even===undefined) + if (rounded < TickMath.MIN_TICK) return rounded + tickSpacing + else if (rounded > TickMath.MAX_TICK) return rounded - tickSpacing + else return rounded + if(even) + if (roundedEven < TickMath.MIN_TICK) return roundedEven + (tickSpacing*2) + else if (roundedEven > TickMath.MAX_TICK) return roundedEven - (tickSpacing*2) + else return roundedEven + else + if (roundedOdd < TickMath.MIN_TICK) return roundedOdd + (tickSpacing*2) + else if (roundedOdd > TickMath.MAX_TICK) return roundedOdd - (tickSpacing*2) + else return roundedOdd +} +// if(even===undefined) +// return rounded +// if(even) +// return roundedEven +// else +// return roundedOdd diff --git a/src/utils/priceTickConversions.ts b/src/utils/priceTickConversions.ts new file mode 100644 index 0000000..86ea32d --- /dev/null +++ b/src/utils/priceTickConversions.ts @@ -0,0 +1,51 @@ +import { Price, Token,ChainId,wrappedCurrency } from '../index' +import JSBI from 'jsbi' +import { Q192 } from '../internalConstants' +// import { encodeSqrtRatioX96 } from './encodeSqrtRatioX96' +import { TickMath } from './tickMath' +import { encodeSqrtRatioX96 } from './encodeSqrtRatioX96' + +/** + * Returns a price object corresponding to the input tick and the base/quote token + * Inputs must be tokens because the address order is used to interpret the price represented by the tick + * @param baseToken the base token of the price + * @param quoteToken the quote token of the price + * @param tick the tick for which to return the price + */ +export function tickToPrice(baseToken: Token, quoteToken: Token, tick: number): Price { + const sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick) + + const ratioX192 = JSBI.multiply(sqrtRatioX96, sqrtRatioX96) + + return baseToken.sortsBefore(quoteToken) + ? new Price(baseToken, quoteToken, Q192, ratioX192) + : new Price(baseToken, quoteToken, ratioX192, Q192) +} + +/** + * Returns the first tick for which the given price is greater than or equal to the tick price + * @param price for which to return the closest tick that represents a price less than or equal to the input price, + * i.e. the price of the returned tick is less than or equal to the input price + */ +export function priceToClosestTick(price: Price,chainId:ChainId): number { + const baseCurrency=wrappedCurrency(price.baseCurrency,chainId); +const quoteCurrency=wrappedCurrency(price.quoteCurrency,chainId); + const sorted = baseCurrency.sortsBefore(quoteCurrency) + + const sqrtRatioX96 = sorted + ? encodeSqrtRatioX96(price.numerator, price.denominator) + : encodeSqrtRatioX96(price.denominator, price.numerator) + + let tick = TickMath.getTickAtSqrtRatio(sqrtRatioX96) + const nextTickPrice = tickToPrice(baseCurrency, quoteCurrency, tick + 1) + if (sorted) { + if (!price.lessThan(nextTickPrice)) { + tick++ + } + } else { + if (!price.greaterThan(nextTickPrice)) { + tick++ + } + } + return tick +} diff --git a/src/utils/sqrtPriceMath.ts b/src/utils/sqrtPriceMath.ts new file mode 100644 index 0000000..74776aa --- /dev/null +++ b/src/utils/sqrtPriceMath.ts @@ -0,0 +1,119 @@ +import { MaxUint256 } from '../constants' +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { ONE, ZERO, Q96 } from '../internalConstants' +import { FullMath } from './fullMath' + +const MaxUint160 = JSBI.subtract(JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(160)), ONE) + +function multiplyIn256(x: JSBI, y: JSBI): JSBI { + const product = JSBI.multiply(x, y) + return JSBI.bitwiseAnd(product, MaxUint256) +} + +function addIn256(x: JSBI, y: JSBI): JSBI { + const sum = JSBI.add(x, y) + return JSBI.bitwiseAnd(sum, MaxUint256) +} + +export abstract class SqrtPriceMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static getAmount0Delta(sqrtRatioAX96: JSBI, sqrtRatioBX96: JSBI, liquidity: JSBI, roundUp: boolean): JSBI { + if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] + } + + const numerator1 = JSBI.leftShift(liquidity, JSBI.BigInt(96)) + const numerator2 = JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96) + + return roundUp + ? FullMath.mulDivRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), ONE, sqrtRatioAX96) + : JSBI.divide(JSBI.divide(JSBI.multiply(numerator1, numerator2), sqrtRatioBX96), sqrtRatioAX96) + } + + public static getAmount1Delta(sqrtRatioAX96: JSBI, sqrtRatioBX96: JSBI, liquidity: JSBI, roundUp: boolean): JSBI { + if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] + } + + return roundUp + ? FullMath.mulDivRoundingUp(liquidity, JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96), Q96) + : JSBI.divide(JSBI.multiply(liquidity, JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96)), Q96) + } + + public static getNextSqrtPriceFromInput(sqrtPX96: JSBI, liquidity: JSBI, amountIn: JSBI, zeroForOne: boolean): JSBI { + invariant(JSBI.greaterThan(sqrtPX96, ZERO)) + invariant(JSBI.greaterThan(liquidity, ZERO)) + + return zeroForOne + ? this.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : this.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) + } + + public static getNextSqrtPriceFromOutput( + sqrtPX96: JSBI, + liquidity: JSBI, + amountOut: JSBI, + zeroForOne: boolean + ): JSBI { + invariant(JSBI.greaterThan(sqrtPX96, ZERO)) + invariant(JSBI.greaterThan(liquidity, ZERO)) + + return zeroForOne + ? this.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : this.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) + } + + private static getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96: JSBI, + liquidity: JSBI, + amount: JSBI, + add: boolean + ): JSBI { + if (JSBI.equal(amount, ZERO)) return sqrtPX96 + const numerator1 = JSBI.leftShift(liquidity, JSBI.BigInt(96)) + + if (add) { + let product = multiplyIn256(amount, sqrtPX96) + if (JSBI.equal(JSBI.divide(product, amount), sqrtPX96)) { + const denominator = addIn256(numerator1, product) + if (JSBI.greaterThanOrEqual(denominator, numerator1)) { + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator) + } + } + + return FullMath.mulDivRoundingUp(numerator1, ONE, JSBI.add(JSBI.divide(numerator1, sqrtPX96), amount)) + } else { + let product = multiplyIn256(amount, sqrtPX96) + + invariant(JSBI.equal(JSBI.divide(product, amount), sqrtPX96)) + invariant(JSBI.greaterThan(numerator1, product)) + const denominator = JSBI.subtract(numerator1, product) + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator) + } + } + + private static getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96: JSBI, + liquidity: JSBI, + amount: JSBI, + add: boolean + ): JSBI { + if (add) { + const quotient = JSBI.lessThanOrEqual(amount, MaxUint160) + ? JSBI.divide(JSBI.leftShift(amount, JSBI.BigInt(96)), liquidity) + : JSBI.divide(JSBI.multiply(amount, Q96), liquidity) + + return JSBI.add(sqrtPX96, quotient) + } else { + const quotient = FullMath.mulDivRoundingUp(amount, Q96, liquidity) + + invariant(JSBI.greaterThan(sqrtPX96, quotient)) + return JSBI.subtract(sqrtPX96, quotient) + } + } +} diff --git a/src/utils/tickList.ts b/src/utils/tickList.ts new file mode 100644 index 0000000..d6988ac --- /dev/null +++ b/src/utils/tickList.ts @@ -0,0 +1,133 @@ +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { Tick } from '../entities/tick' +import { ZERO } from '../internalConstants' +import { isSorted } from './isSorted' + +function tickComparator(a: Tick, b: Tick) { + return a.index - b.index +} + +/** + * Utility methods for interacting with sorted lists of ticks + */ +export abstract class TickList { + /** + * Cannot be constructed + */ + private constructor() {} + + public static validateList(ticks: Tick[], tickSpacing: number) { + invariant(tickSpacing > 0, 'TICK_SPACING_NONZERO') + // ensure ticks are spaced appropriately + invariant( + ticks.every(({ index }) => index % tickSpacing === 0), + 'TICK_SPACING' + ) + + // ensure tick liquidity deltas sum to 0 + invariant( + JSBI.equal( + ticks.reduce((accumulator, { liquidityNet }) => JSBI.add(accumulator, liquidityNet), ZERO), + ZERO + ), + 'ZERO_NET' + ) + + invariant(isSorted(ticks, tickComparator), 'SORTED') + } + + public static isBelowSmallest(ticks: readonly Tick[], tick: number): boolean { + invariant(ticks.length > 0, 'LENGTH') + return tick < ticks[0].index + } + + public static isAtOrAboveLargest(ticks: readonly Tick[], tick: number): boolean { + invariant(ticks.length > 0, 'LENGTH') + return tick >= ticks[ticks.length - 1].index + } + + public static getTick(ticks: readonly Tick[], index: number): Tick { + const tick = ticks[this.binarySearch(ticks, index)] + invariant(tick.index === index, 'NOT_CONTAINED') + return tick + } + + /** + * Finds the largest tick in the list of ticks that is less than or equal to tick + * @param ticks list of ticks + * @param tick tick to find the largest tick that is less than or equal to tick + * @private + */ + private static binarySearch(ticks: readonly Tick[], tick: number): number { + invariant(!this.isBelowSmallest(ticks, tick), 'BELOW_SMALLEST') + + let l = 0 + let r = ticks.length - 1 + let i + while (true) { + i = Math.floor((l + r) / 2) + + if (ticks[i].index <= tick && (i === ticks.length - 1 || ticks[i + 1].index > tick)) { + return i + } + + if (ticks[i].index < tick) { + l = i + 1 + } else { + r = i - 1 + } + } + } + + public static nextInitializedTick(ticks: readonly Tick[], tick: number, lte: boolean): Tick { + if (lte) { + invariant(!TickList.isBelowSmallest(ticks, tick), 'BELOW_SMALLEST') + if (TickList.isAtOrAboveLargest(ticks, tick)) { + return ticks[ticks.length - 1] + } + const index = this.binarySearch(ticks, tick) + return ticks[index] + } else { + invariant(!this.isAtOrAboveLargest(ticks, tick), 'AT_OR_ABOVE_LARGEST') + if (this.isBelowSmallest(ticks, tick)) { + return ticks[0] + } + const index = this.binarySearch(ticks, tick) + return ticks[index + 1] + } + } + + public static nextInitializedTickWithinOneWord( + ticks: readonly Tick[], + tick: number, + lte: boolean, + tickSpacing: number + ): [number, boolean] { + const compressed = Math.floor(tick / tickSpacing) // matches rounding in the code + + if (lte) { + const wordPos = compressed >> 8 + const minimum = (wordPos << 8) * tickSpacing + + if (TickList.isBelowSmallest(ticks, tick)) { + return [minimum, false] + } + + const index = TickList.nextInitializedTick(ticks, tick, lte).index + const nextInitializedTick = Math.max(minimum, index) + return [nextInitializedTick, nextInitializedTick === index] + } else { + const wordPos = (compressed + 1) >> 8 + const maximum = (((wordPos + 1) << 8) - 1) * tickSpacing + + if (this.isAtOrAboveLargest(ticks, tick)) { + return [maximum, false] + } + + const index = this.nextInitializedTick(ticks, tick, lte).index + const nextInitializedTick = Math.min(maximum, index) + return [nextInitializedTick, nextInitializedTick === index] + } + } +} diff --git a/src/utils/tickMath.ts b/src/utils/tickMath.ts new file mode 100644 index 0000000..0706d7e --- /dev/null +++ b/src/utils/tickMath.ts @@ -0,0 +1,130 @@ +import { MaxUint256 } from '../constants' +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { ONE, ZERO } from '../internalConstants' +import { mostSignificantBit } from './mostSignificantBit' + +function mulShift(val: JSBI, mulBy: string): JSBI { + return JSBI.signedRightShift(JSBI.multiply(val, JSBI.BigInt(mulBy)), JSBI.BigInt(128)) +} + +const Q32 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(32)) + +export abstract class TickMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + /** + * The minimum tick that can be used on any pool. + */ + public static MIN_TICK: number = -887272 + /** + * The maximum tick that can be used on any pool. + */ + public static MAX_TICK: number = -TickMath.MIN_TICK + + /** + * The sqrt ratio corresponding to the minimum tick that could be used on any pool. + */ + public static MIN_SQRT_RATIO: JSBI = JSBI.BigInt('4295128739') + /** + * The sqrt ratio corresponding to the maximum tick that could be used on any pool. + */ + public static MAX_SQRT_RATIO: JSBI = JSBI.BigInt('1461446703485210103287273052203988822378723970342') + + /** + * Returns the sqrt ratio as a Q64.96 for the given tick. The sqrt ratio is computed as sqrt(1.0001)^tick + * @param tick the tick for which to compute the sqrt ratio + */ + public static getSqrtRatioAtTick(tick: number): JSBI { + invariant(tick >= TickMath.MIN_TICK && tick <= TickMath.MAX_TICK && Number.isInteger(tick), 'TICK') + const absTick: number = tick < 0 ? tick * -1 : tick + + let ratio: JSBI = + (absTick & 0x1) != 0 + ? JSBI.BigInt('0xfffcb933bd6fad37aa2d162d1a594001') + : JSBI.BigInt('0x100000000000000000000000000000000') + if ((absTick & 0x2) != 0) ratio = mulShift(ratio, '0xfff97272373d413259a46990580e213a') + if ((absTick & 0x4) != 0) ratio = mulShift(ratio, '0xfff2e50f5f656932ef12357cf3c7fdcc') + if ((absTick & 0x8) != 0) ratio = mulShift(ratio, '0xffe5caca7e10e4e61c3624eaa0941cd0') + if ((absTick & 0x10) != 0) ratio = mulShift(ratio, '0xffcb9843d60f6159c9db58835c926644') + if ((absTick & 0x20) != 0) ratio = mulShift(ratio, '0xff973b41fa98c081472e6896dfb254c0') + if ((absTick & 0x40) != 0) ratio = mulShift(ratio, '0xff2ea16466c96a3843ec78b326b52861') + if ((absTick & 0x80) != 0) ratio = mulShift(ratio, '0xfe5dee046a99a2a811c461f1969c3053') + if ((absTick & 0x100) != 0) ratio = mulShift(ratio, '0xfcbe86c7900a88aedcffc83b479aa3a4') + if ((absTick & 0x200) != 0) ratio = mulShift(ratio, '0xf987a7253ac413176f2b074cf7815e54') + if ((absTick & 0x400) != 0) ratio = mulShift(ratio, '0xf3392b0822b70005940c7a398e4b70f3') + if ((absTick & 0x800) != 0) ratio = mulShift(ratio, '0xe7159475a2c29b7443b29c7fa6e889d9') + if ((absTick & 0x1000) != 0) ratio = mulShift(ratio, '0xd097f3bdfd2022b8845ad8f792aa5825') + if ((absTick & 0x2000) != 0) ratio = mulShift(ratio, '0xa9f746462d870fdf8a65dc1f90e061e5') + if ((absTick & 0x4000) != 0) ratio = mulShift(ratio, '0x70d869a156d2a1b890bb3df62baf32f7') + if ((absTick & 0x8000) != 0) ratio = mulShift(ratio, '0x31be135f97d08fd981231505542fcfa6') + if ((absTick & 0x10000) != 0) ratio = mulShift(ratio, '0x9aa508b5b7a84e1c677de54f3e99bc9') + if ((absTick & 0x20000) != 0) ratio = mulShift(ratio, '0x5d6af8dedb81196699c329225ee604') + if ((absTick & 0x40000) != 0) ratio = mulShift(ratio, '0x2216e584f5fa1ea926041bedfe98') + if ((absTick & 0x80000) != 0) ratio = mulShift(ratio, '0x48a170391f7dc42444e8fa2') + + if (tick > 0) ratio = JSBI.divide(MaxUint256, ratio) + + // back to Q96 + return JSBI.greaterThan(JSBI.remainder(ratio, Q32), ZERO) + ? JSBI.add(JSBI.divide(ratio, Q32), ONE) + : JSBI.divide(ratio, Q32) + } + + /** + * Returns the tick corresponding to a given sqrt ratio, s.t. #getSqrtRatioAtTick(tick) <= sqrtRatioX96 + * and #getSqrtRatioAtTick(tick + 1) > sqrtRatioX96 + * @param sqrtRatioX96 the sqrt ratio as a Q64.96 for which to compute the tick + */ + public static getTickAtSqrtRatio(sqrtRatioX96: JSBI): number { + invariant( + JSBI.greaterThanOrEqual(sqrtRatioX96, TickMath.MIN_SQRT_RATIO) && + JSBI.lessThan(sqrtRatioX96, TickMath.MAX_SQRT_RATIO), + 'SQRT_RATIO' + ) + + const sqrtRatioX128 = JSBI.leftShift(sqrtRatioX96, JSBI.BigInt(32)) + + const msb = mostSignificantBit(sqrtRatioX128) + + let r: JSBI + if (JSBI.greaterThanOrEqual(JSBI.BigInt(msb), JSBI.BigInt(128))) { + r = JSBI.signedRightShift(sqrtRatioX128, JSBI.BigInt(msb - 127)) + } else { + r = JSBI.leftShift(sqrtRatioX128, JSBI.BigInt(127 - msb)) + } + + let log_2: JSBI = JSBI.leftShift(JSBI.subtract(JSBI.BigInt(msb), JSBI.BigInt(128)), JSBI.BigInt(64)) + + for (let i = 0; i < 14; i++) { + r = JSBI.signedRightShift(JSBI.multiply(r, r), JSBI.BigInt(127)) + const f = JSBI.signedRightShift(r, JSBI.BigInt(128)) + log_2 = JSBI.bitwiseOr(log_2, JSBI.leftShift(f, JSBI.BigInt(63 - i))) + r = JSBI.signedRightShift(r, f) + } + + const log_sqrt10001 = JSBI.multiply(log_2, JSBI.BigInt('255738958999603826347141')) + + const tickLow = JSBI.toNumber( + JSBI.signedRightShift( + JSBI.subtract(log_sqrt10001, JSBI.BigInt('3402992956809132418596140100660247210')), + JSBI.BigInt(128) + ) + ) + const tickHigh = JSBI.toNumber( + JSBI.signedRightShift( + JSBI.add(log_sqrt10001, JSBI.BigInt('291339464771989622907027621153398088495')), + JSBI.BigInt(128) + ) + ) + + return tickLow === tickHigh + ? tickLow + : JSBI.lessThanOrEqual(TickMath.getSqrtRatioAtTick(tickHigh), sqrtRatioX96) + ? tickHigh + : tickLow + } +} diff --git a/test/pool.test.ts b/test/pool.test.ts new file mode 100644 index 0000000..4b19016 --- /dev/null +++ b/test/pool.test.ts @@ -0,0 +1,28 @@ +import { getCreate2Address } from "@ethersproject/address" +import { keccak256 } from "@ethersproject/solidity" +import { V2_POOL_INIT_CODE_HASH } from "../src/constants" +import { defaultAbiCoder } from '@ethersproject/abi' + +// TODO: replace the provider in these tests +describe('data', () => { + it('Token', async () => { + const salt0=keccak256( + ['bytes'], + [defaultAbiCoder.encode(['address', 'address'], ["0xC168E40227E4ebD8C1caE80F7a55a4F0e6D66C97".toLowerCase(), "0x16ECCfDbb4eE1A85A33f3A9B21175Cd7Ae753dB4".toLowerCase()])] + ) + console.log(getCreate2Address( + "0x68E776B2369696e589254cfc6d2ce4B29385952D", + salt0, + V2_POOL_INIT_CODE_HASH[137].toLowerCase() + )) + const salt1=keccak256( + ['bytes'], + [defaultAbiCoder.encode(['address', 'address'], ["0x16ECCfDbb4eE1A85A33f3A9B21175Cd7Ae753dB4".toLowerCase(), "0xC168E40227E4ebD8C1caE80F7a55a4F0e6D66C97".toLowerCase()])] + ) + console.log(getCreate2Address( + "0x68E776B2369696e589254cfc6d2ce4B29385952D", + salt1, + V2_POOL_INIT_CODE_HASH[137].toLowerCase() + )) + }) +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e6e713d..5bffa30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -904,6 +904,21 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@ethersproject/abi@^5.0.12": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/abi@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.4.1.tgz#6ac28fafc9ef6f5a7a37e30356a2eb31fa05d39b" @@ -932,6 +947,19 @@ "@ethersproject/transactions" "^5.4.0" "@ethersproject/web" "^5.4.0" +"@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + "@ethersproject/abstract-signer@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81" @@ -943,6 +971,17 @@ "@ethersproject/logger" "^5.4.0" "@ethersproject/properties" "^5.4.0" +"@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3" @@ -954,6 +993,17 @@ "@ethersproject/logger" "^5.4.0" "@ethersproject/rlp" "^5.4.0" +"@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/base64@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.4.0.tgz#7252bf65295954c9048c7ca5f43e5c86441b2a9a" @@ -961,6 +1011,13 @@ dependencies: "@ethersproject/bytes" "^5.4.0" +"@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/basex@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.4.0.tgz#0a2da0f4e76c504a94f2b21d3161ed9438c7f8a6" @@ -978,6 +1035,15 @@ "@ethersproject/logger" "^5.4.0" bn.js "^4.11.9" +"@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + "@ethersproject/bytes@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e" @@ -985,6 +1051,13 @@ dependencies: "@ethersproject/logger" "^5.4.0" +"@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/constants@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a" @@ -992,6 +1065,13 @@ dependencies: "@ethersproject/bignumber" "^5.4.0" +"@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/contracts@^5.0.2": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470" @@ -1022,6 +1102,21 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" +"@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/keccak256@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.4.0.tgz#7143b8eea4976080241d2bd92e3b1f1bf7025318" @@ -1030,11 +1125,24 @@ "@ethersproject/bytes" "^5.4.0" js-sha3 "0.5.7" +"@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + "@ethersproject/logger@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.1.tgz#503bd33683538b923c578c07d1c2c0dd18672054" integrity sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A== +"@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + "@ethersproject/networks@^5.0.2", "@ethersproject/networks@^5.4.0": version "5.4.2" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35" @@ -1042,6 +1150,13 @@ dependencies: "@ethersproject/logger" "^5.4.0" +"@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.4.1.tgz#9f051f976ce790142c6261ccb7b826eaae1f2f36" @@ -1049,6 +1164,13 @@ dependencies: "@ethersproject/logger" "^5.4.0" +"@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/providers@^5.0.5": version "5.4.5" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.5.tgz#eb2ea2a743a8115f79604a8157233a3a2c832928" @@ -1090,6 +1212,14 @@ "@ethersproject/bytes" "^5.4.0" "@ethersproject/logger" "^5.4.0" +"@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371" @@ -1111,6 +1241,18 @@ elliptic "6.5.4" hash.js "1.1.7" +"@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + "@ethersproject/solidity@^5.0.2": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.4.0.tgz#1305e058ea02dc4891df18b33232b11a14ece9ec" @@ -1131,6 +1273,15 @@ "@ethersproject/constants" "^5.4.0" "@ethersproject/logger" "^5.4.0" +"@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/transactions@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.4.0.tgz#a159d035179334bd92f340ce0f77e83e9e1522e0" @@ -1146,6 +1297,21 @@ "@ethersproject/rlp" "^5.4.0" "@ethersproject/signing-key" "^5.4.0" +"@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/web@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.4.0.tgz#49fac173b96992334ed36a175538ba07a7413d1f" @@ -1157,6 +1323,17 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" +"@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@jest/console@^24.7.1", "@jest/console@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" @@ -2034,6 +2211,11 @@ bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4251,6 +4433,11 @@ js-sha3@0.5.7: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc= +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"