From 20ae24a09eafc95e94773adb76061c17f692e449 Mon Sep 17 00:00:00 2001 From: - <-> Date: Fri, 19 Jan 2024 18:54:40 +0000 Subject: [PATCH] Add Pancakeswap V3 trades support --- uniswap/assets/uniswap-v3/factory.abi | 2 +- uniswap/assets/uniswap-v3/multicall.abi | 2 +- uniswap/assets/uniswap-v3/pool.abi | 2 +- .../assets/uniswap-v3/quoter-pancakeswap.abi | 285 ++++ .../assets/uniswap-v3/router-pancakeswap.abi | 1271 +++++++++++++++++ uniswap/cli.py | 4 +- uniswap/constants.py | 31 +- uniswap/fee.py | 3 + uniswap/uniswap.py | 342 +++-- 9 files changed, 1826 insertions(+), 116 deletions(-) create mode 100644 uniswap/assets/uniswap-v3/quoter-pancakeswap.abi create mode 100644 uniswap/assets/uniswap-v3/router-pancakeswap.abi diff --git a/uniswap/assets/uniswap-v3/factory.abi b/uniswap/assets/uniswap-v3/factory.abi index 63f8e7a..efbd50a 100644 --- a/uniswap/assets/uniswap-v3/factory.abi +++ b/uniswap/assets/uniswap-v3/factory.abi @@ -233,4 +233,4 @@ "stateMutability": "nonpayable", "type": "function" } -] \ No newline at end of file +] diff --git a/uniswap/assets/uniswap-v3/multicall.abi b/uniswap/assets/uniswap-v3/multicall.abi index 2760624..3b35c29 100644 --- a/uniswap/assets/uniswap-v3/multicall.abi +++ b/uniswap/assets/uniswap-v3/multicall.abi @@ -310,4 +310,4 @@ "stateMutability": "nonpayable", "type": "function" } -] \ No newline at end of file +] diff --git a/uniswap/assets/uniswap-v3/pool.abi b/uniswap/assets/uniswap-v3/pool.abi index 49cc338..905e300 100644 --- a/uniswap/assets/uniswap-v3/pool.abi +++ b/uniswap/assets/uniswap-v3/pool.abi @@ -985,4 +985,4 @@ "stateMutability": "view", "type": "function" } -] \ No newline at end of file +] diff --git a/uniswap/assets/uniswap-v3/quoter-pancakeswap.abi b/uniswap/assets/uniswap-v3/quoter-pancakeswap.abi new file mode 100644 index 0000000..3df310a --- /dev/null +++ b/uniswap/assets/uniswap-v3/quoter-pancakeswap.abi @@ -0,0 +1,285 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_deployer", + "type": "address" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + } + ], + "name": "pancakeV3SwapCallback", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "name": "quoteExactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksCrossedList", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IQuoterV2.QuoteExactInputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactInputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksCrossed", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "quoteExactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksCrossedList", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IQuoterV2.QuoteExactOutputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactOutputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksCrossed", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/uniswap/assets/uniswap-v3/router-pancakeswap.abi b/uniswap/assets/uniswap-v3/router-pancakeswap.abi new file mode 100644 index 0000000..ed0b4c5 --- /dev/null +++ b/uniswap/assets/uniswap-v3/router-pancakeswap.abi @@ -0,0 +1,1271 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_factoryV2", + "type": "address" + }, + { + "internalType": "address", + "name": "_deployer", + "type": "address" + }, + { + "internalType": "address", + "name": "_factoryV3", + "type": "address" + }, + { + "internalType": "address", + "name": "_positionManager", + "type": "address" + }, + { + "internalType": "address", + "name": "_stableFactory", + "type": "address" + }, + { + "internalType": "address", + "name": "_stableInfo", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "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" + }, + { + "indexed": true, + "internalType": "address", + "name": "info", + "type": "address" + } + ], + "name": "SetStableSwap", + "type": "event" + }, + { + "inputs": [], + "name": "WETH9", + "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": [ + { + "internalType": "bytes[]", + "name": "paths", + "type": "bytes[]" + }, + { + "internalType": "uint128[]", + "name": "amounts", + "type": "uint128[]" + }, + { + "internalType": "uint24", + "name": "maximumTickDivergence", + "type": "uint24" + }, + { + "internalType": "uint32", + "name": "secondsAgo", + "type": "uint32" + } + ], + "name": "checkOracleSlippage", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint24", + "name": "maximumTickDivergence", + "type": "uint24" + }, + { + "internalType": "uint32", + "name": "secondsAgo", + "type": "uint32" + } + ], + "name": "checkOracleSlippage", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deployer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "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": "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": [ + { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "flag", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "exactInputStableSwap", + "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": [ + { + "internalType": "address[]", + "name": "path", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "flag", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMax", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "exactOutputStableSwap", + "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": [ + { + "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": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "pancakeV3SwapCallback", + "outputs": [], + "stateMutability": "nonpayable", + "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": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "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": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_info", + "type": "address" + } + ], + "name": "setStableSwap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stableSwapFactory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stableSwapInfo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "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": "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": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "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" + }, + { + "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": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "wrapETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/uniswap/cli.py b/uniswap/cli.py index 78302cf..dd7bc57 100644 --- a/uniswap/cli.py +++ b/uniswap/cli.py @@ -81,7 +81,9 @@ def price( else: decimals = uni.get_token(token_in).decimals quantity = 10**decimals - price = uni.get_price_input(token_in, token_out, qty=quantity, fee=FeeTier.TIER_3000) + price = uni.get_price_input( + token_in, token_out, qty=quantity, fee=FeeTier.TIER_3000 + ) if raw: click.echo(price) else: diff --git a/uniswap/constants.py b/uniswap/constants.py index b30e9ab..82b3759 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -31,7 +31,7 @@ 421611: "arbitrum_testnet", 1666600000: "harmony_mainnet", 1666700000: "harmony_testnet", - 11155111: "sepolia" + 11155111: "sepolia", } _factory_contract_addresses_v1 = { @@ -56,7 +56,12 @@ # SushiSwap on Harmony "harmony_mainnet": "0xc35DADB65012eC5796536bD9864eD8773aBc74C4", "harmony_testnet": "0xc35DADB65012eC5796536bD9864eD8773aBc74C4", - "sepolia": "0x7E0987E5b3a30e3f2828572Bb659A548460a3003" + "sepolia": "0x7E0987E5b3a30e3f2828572Bb659A548460a3003", +} + +_factory_contract_addresses_v3 = { + "mainnet": "0x1F98431c8aD98523631AE4a59f267346ea31F984", + "binance": "0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865", } _router_contract_addresses_v2 = { @@ -70,10 +75,30 @@ # SushiSwap on Harmony "harmony_mainnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", "harmony_testnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", - #sepolia tesnet router address + # sepolia tesnet router address "sepolia": "0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008", } +_router_contract_addresses_v3 = { + "mainnet": "0xE592427A0AEce92De3Edee1F18E0157C05861564", + "binance": "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4", +} + +_quoter_contract_addresses_v3 = { + "mainnet": "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6", + "binance": "0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997", +} + +_posmanager_contract_addresses_v3 = { + "mainnet": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", + "binance": "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364", +} + +_router_special_addresses_v3 = { + "mainnet": "0x0000000000000000000000000000000000000000", + "binance": "0x0000000000000000000000000000000000000002", +} + MAX_UINT_128 = (2**128) - 1 # Source: https://github.com/Uniswap/v3-core/blob/v1.0.0/contracts/libraries/TickMath.sol#L8-L11 diff --git a/uniswap/fee.py b/uniswap/fee.py index 0005d84..286ae56 100644 --- a/uniswap/fee.py +++ b/uniswap/fee.py @@ -25,6 +25,9 @@ class FeeTier(enum.IntEnum): TIER_3000 = 3000 TIER_10000 = 10000 + # Pancakeswap tier + TIER_2500 = 2500 + def validate_fee_tier(fee: Optional[int], version: int) -> int: """ diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index 5edbc85..b7960cf 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -22,6 +22,7 @@ from web3.contract import Contract from web3.contract.contract import ContractFunction from web3.exceptions import BadFunctionCallOutput, ContractLogicError +from web3.gas_strategies.rpc import rpc_gas_price_strategy from web3.types import ( Nonce, TxParams, @@ -37,8 +38,13 @@ WETH9_ADDRESS, _factory_contract_addresses_v1, _factory_contract_addresses_v2, + _factory_contract_addresses_v3, _netid_to_name, _router_contract_addresses_v2, + _router_contract_addresses_v3, + _quoter_contract_addresses_v3, + _posmanager_contract_addresses_v3, + _router_special_addresses_v3, _tick_bitmap_range, _tick_spacing, ) @@ -89,8 +95,11 @@ def __init__( default_slippage: float = 0.01, use_estimate_gas: bool = True, # use_eip1559: bool = True, + use_rpc_gas_price: bool = False, factory_contract_addr: Optional[str] = None, router_contract_addr: Optional[str] = None, + quoter_contract_addr: Optional[str] = None, + posmanager_contract_addr: Optional[str] = None, enable_caching: bool = False, ) -> None: """ @@ -129,6 +138,8 @@ def __init__( if not provider: provider = os.environ["PROVIDER"] self.w3 = Web3(Web3.HTTPProvider(provider, request_kwargs={"timeout": 60})) + if use_rpc_gas_price: + self.w3.eth.set_gas_price_strategy(rpc_gas_price_strategy) if enable_caching: self.w3.middleware_onion.inject(_get_eth_simple_cache_middleware(), layer=0) @@ -181,30 +192,54 @@ def __init__( ) elif self.version == 3: # https://github.com/Uniswap/uniswap-v3-periphery/blob/main/deploys.md - factory_contract_address = _str_to_addr( - "0x1F98431c8aD98523631AE4a59f267346ea31F984" + def def_address(addresses: Dict[str, str]) -> str: + return addresses.get(self.netname) or addresses["mainnet"] + + if self.netname == "binance": + router_abi_name = "uniswap-v3/router-pancakeswap" + quoter_abi_name = "uniswap-v3/quoter-pancakeswap" + else: + router_abi_name = "uniswap-v3/router" + quoter_abi_name = "uniswap-v3/quoter" + + if router_contract_addr is None: + router_contract_addr = def_address(_router_contract_addresses_v3) + + self.router_address = _str_to_addr(router_contract_addr) + self.router = _load_contract( + self.w3, abi_name=router_abi_name, address=self.router_address ) + + # Special address inside a smart contract is equivalent to address(this) + self.router_spec_addr = def_address(_router_special_addresses_v3) + + if factory_contract_addr is None: + factory_contract_addr = def_address(_factory_contract_addresses_v3) self.factory_contract = _load_contract( - self.w3, abi_name="uniswap-v3/factory", address=factory_contract_address - ) - quoter_addr = _str_to_addr("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6") - self.router_address = _str_to_addr( - "0xE592427A0AEce92De3Edee1F18E0157C05861564" + self.w3, + abi_name="uniswap-v3/factory", + address=_str_to_addr(factory_contract_addr), ) + + if quoter_contract_addr is None: + quoter_contract_addr = def_address(_quoter_contract_addresses_v3) self.quoter = _load_contract( - self.w3, abi_name="uniswap-v3/quoter", address=quoter_addr - ) - self.router = _load_contract( - self.w3, abi_name="uniswap-v3/router", address=self.router_address - ) - self.positionManager_addr = _str_to_addr( - "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" + self.w3, + abi_name=quoter_abi_name, + address=_str_to_addr(quoter_contract_addr), ) + + if posmanager_contract_addr is None: + posmanager_contract_addr = def_address( + _posmanager_contract_addresses_v3 + ) + self.positionManager_addr = _str_to_addr(posmanager_contract_addr) self.nonFungiblePositionManager = _load_contract( self.w3, abi_name="uniswap-v3/nonFungiblePositionManager", address=self.positionManager_addr, ) + if self.netname == "arbitrum": multicall2_addr = _str_to_addr( "0x50075F151ABC5B6B448b1272A0a1cFb5CFA25828" @@ -341,9 +376,9 @@ def _get_token_token_input_price( # FIXME: How to calculate this properly? See https://docs.uniswap.org/reference/libraries/SqrtPriceMath sqrtPriceLimitX96 = 0 - price = self.quoter.functions.quoteExactInputSingle( + price = self._quoter_quoteExactInputSingle( token0, token1, fee, qty, sqrtPriceLimitX96 - ).call() + ) else: raise ValueError("function not supported for this version of Uniswap") return price @@ -430,13 +465,51 @@ def _get_token_token_output_price( # - https://docs.uniswap.org/reference/libraries/SqrtPriceMath # - https://github.com/Uniswap/uniswap-v3-sdk/blob/main/src/swapRouter.ts sqrtPriceLimitX96 = 0 - price = self.quoter.functions.quoteExactOutputSingle( + price = self._quoter_quoteExactOutputSingle( token0, token1, fee, qty, sqrtPriceLimitX96 - ).call() + ) else: raise ValueError # pragma: no cover return price + def _quoter_quoteExactOutputSingle( + self, + token0: AddressLike, + token1: AddressLike, + fee: int, + qty: int, + sqrtPriceLimitX96: int, + ) -> int: + if self.netname == "binance": + result = self.quoter.functions.quoteExactOutputSingle( + (token0, token1, qty, fee, sqrtPriceLimitX96) + ).call()[0] + return int(result) + else: + result = self.quoter.functions.quoteExactOutputSingle( + token0, token1, fee, qty, sqrtPriceLimitX96 + ).call() + return int(result) + + def _quoter_quoteExactInputSingle( + self, + token0: AddressLike, + token1: AddressLike, + fee: int, + qty: int, + sqrtPriceLimitX96: int, + ) -> int: + if self.netname == "binance": + result = self.quoter.functions.quoteExactInputSingle( + (token0, token1, qty, fee, sqrtPriceLimitX96) + ).call()[0] + return int(result) + else: + result = self.quoter.functions.quoteExactInputSingle( + token0, token1, fee, qty, sqrtPriceLimitX96 + ).call() + return int(result) + # ------ Make Trade ---------------------------------------------------------------- @check_approval def make_trade( @@ -578,19 +651,27 @@ def _eth_to_token_swap_input( ) sqrtPriceLimitX96 = 0 + deadline = self._deadline() + args = { + "tokenIn": self.get_weth_address(), + "tokenOut": output_token, + "fee": fee, + "recipient": recipient, + "deadline": deadline, + "amountIn": qty, + "amountOutMinimum": min_tokens_bought, + "sqrtPriceLimitX96": sqrtPriceLimitX96, + } + self._patch_exact_single_args(args) + + if self.netname == "binance": + swap_func = self._v3_multicall_deadline_wrapper( + "exactInputSingle", deadline, [args] + ) + else: + swap_func = self.router.functions.exactInputSingle(args) return self._build_and_send_tx( - self.router.functions.exactInputSingle( - { - "tokenIn": self.get_weth_address(), - "tokenOut": output_token, - "fee": fee, - "recipient": recipient, - "deadline": self._deadline(), - "amountIn": qty, - "amountOutMinimum": min_tokens_bought, - "sqrtPriceLimitX96": sqrtPriceLimitX96, - } - ), + swap_func, self._get_tx_params(value=qty), ) else: @@ -658,20 +739,22 @@ def _token_to_eth_swap_input( ) sqrtPriceLimitX96 = 0 + deadline = self._deadline() + args = { + "tokenIn": input_token, + "tokenOut": output_token, + "fee": fee, + "recipient": self.router_spec_addr, + "deadline": deadline, + "amountIn": qty, + "amountOutMinimum": min_tokens_bought, + "sqrtPriceLimitX96": sqrtPriceLimitX96, + } + self._patch_exact_single_args(args) + swap_data = self.router.encodeABI( fn_name="exactInputSingle", - args=[ - ( - input_token, - output_token, - fee, - ETH_ADDRESS, - self._deadline(), - qty, - min_tokens_bought, - sqrtPriceLimitX96, - ) - ], + args=[args], ) unwrap_data = self.router.encodeABI( @@ -679,10 +762,14 @@ def _token_to_eth_swap_input( ) # Multicall - return self._build_and_send_tx( - self.router.functions.multicall([swap_data, unwrap_data]), - self._get_tx_params(), - ) + if self.netname == "binance": + multicall = self.router.functions.multicall( + deadline, [swap_data, unwrap_data] + ) + else: + multicall = self.router.functions.multicall([swap_data, unwrap_data]) + + return self._build_and_send_tx(multicall) else: raise ValueError # pragma: no cover @@ -763,21 +850,26 @@ def _token_to_token_swap_input( ) sqrtPriceLimitX96 = 0 - return self._build_and_send_tx( - self.router.functions.exactInputSingle( - { - "tokenIn": input_token, - "tokenOut": output_token, - "fee": fee, - "recipient": recipient, - "deadline": self._deadline(), - "amountIn": qty, - "amountOutMinimum": min_tokens_bought, - "sqrtPriceLimitX96": sqrtPriceLimitX96, - } - ), - self._get_tx_params(), - ) + deadline = self._deadline() + args = { + "tokenIn": input_token, + "tokenOut": output_token, + "fee": fee, + "recipient": recipient, + "deadline": deadline, + "amountIn": qty, + "amountOutMinimum": min_tokens_bought, + "sqrtPriceLimitX96": sqrtPriceLimitX96, + } + self._patch_exact_single_args(args) + + if self.netname == "binance": + swap_func = self._v3_multicall_deadline_wrapper( + "exactInputSingle", deadline, [args] + ) + else: + swap_func = self.router.functions.exactInputSingle(args) + return self._build_and_send_tx(swap_func) else: raise ValueError # pragma: no cover @@ -835,27 +927,32 @@ def _eth_to_token_swap_output( sqrtPriceLimitX96 = 0 - swap_data = self.router.encodeABI( - fn_name="exactOutputSingle", - args=[ - ( - self.get_weth_address(), - output_token, - fee, - recipient, - self._deadline(), - qty, - amount_in_max, - sqrtPriceLimitX96, - ) - ], - ) + deadline = self._deadline() + args = { + "tokenIn": self.get_weth_address(), + "tokenOut": output_token, + "fee": fee, + "recipient": recipient, + "deadline": deadline, + "amountOut": qty, + "amountInMaximum": amount_in_max, + "sqrtPriceLimitX96": sqrtPriceLimitX96, + } + self._patch_exact_single_args(args) + + swap_data = self.router.encodeABI(fn_name="exactOutputSingle", args=[args]) refund_data = self.router.encodeABI(fn_name="refundETH", args=None) # Multicall + if self.netname == "binance": + multicall = self.router.functions.multicall( + deadline, [swap_data, refund_data] + ) + else: + multicall = self.router.functions.multicall([swap_data, refund_data]) return self._build_and_send_tx( - self.router.functions.multicall([swap_data, refund_data]), + multicall, self._get_tx_params(value=amount_in_max), ) else: @@ -923,20 +1020,22 @@ def _token_to_eth_swap_output( sqrtPriceLimitX96 = 0 + deadline = self._deadline() + args = { + "tokenIn": input_token, + "tokenOut": self.get_weth_address(), + "fee": fee, + "recipient": self.router_spec_addr, + "deadline": deadline, + "amountOut": qty, + "amountInMaximum": amount_in_max, + "sqrtPriceLimitX96": sqrtPriceLimitX96, + } + self._patch_exact_single_args(args) + swap_data = self.router.encodeABI( fn_name="exactOutputSingle", - args=[ - ( - input_token, - self.get_weth_address(), - fee, - ETH_ADDRESS, - self._deadline(), - qty, - amount_in_max, - sqrtPriceLimitX96, - ) - ], + args=[args], ) unwrap_data = self.router.encodeABI( @@ -944,10 +1043,13 @@ def _token_to_eth_swap_output( ) # Multicall - return self._build_and_send_tx( - self.router.functions.multicall([swap_data, unwrap_data]), - self._get_tx_params(), - ) + if self.netname == "binance": + multicall = self.router.functions.multicall( + deadline, [swap_data, unwrap_data] + ) + else: + multicall = self.router.functions.multicall([swap_data, unwrap_data]) + return self._build_and_send_tx(multicall) else: raise ValueError @@ -1019,24 +1121,39 @@ def _token_to_token_swap_output( sqrtPriceLimitX96 = 0 - return self._build_and_send_tx( - self.router.functions.exactOutputSingle( - { - "tokenIn": input_token, - "tokenOut": output_token, - "fee": fee, - "recipient": recipient, - "deadline": self._deadline(), - "amountOut": qty, - "amountInMaximum": amount_in_max, - "sqrtPriceLimitX96": sqrtPriceLimitX96, - }, - ), - self._get_tx_params(), - ) + deadline = self._deadline() + args = { + "tokenIn": input_token, + "tokenOut": output_token, + "fee": fee, + "recipient": recipient, + "deadline": deadline, + "amountOut": qty, + "amountInMaximum": amount_in_max, + "sqrtPriceLimitX96": sqrtPriceLimitX96, + } + self._patch_exact_single_args(args) + + if self.netname == "binance": + swap_func = self._v3_multicall_deadline_wrapper( + "exactOutputSingle", deadline, [args] + ) + else: + swap_func = self.router.functions.exactOutputSingle(args) + return self._build_and_send_tx(swap_func) else: raise ValueError + def _patch_exact_single_args(self, args: Dict) -> None: + if self.netname == "binance": + del args["deadline"] + + def _v3_multicall_deadline_wrapper( + self, fn_name: str, deadline: int, args: List + ) -> ContractFunction: + data = self.router.encodeABI(fn_name=fn_name, args=args) + return self.router.functions.multicall(deadline, [data]) + # ------ Wallet balance ------------------------------------------------------------ def get_eth_balance(self) -> Wei: """Get the balance of ETH for your address.""" @@ -1434,7 +1551,13 @@ def _build_and_send_tx( """Build and send a transaction.""" if not tx_params: tx_params = self._get_tx_params() - transaction = function.build_transaction(tx_params) + + # Without ['gas'] parameter Web3 will call estimate_gas internally + tx_params_gas = tx_params + if "gas" not in tx_params: + tx_params_gas = tx_params.copy() + tx_params_gas["gas"] = 1 + transaction = function.build_transaction(tx_params_gas) if "gas" not in tx_params: # `use_estimate_gas` needs to be True for networks like Arbitrum (can't assume 250000 gas), @@ -1457,6 +1580,7 @@ def _build_and_send_tx( return self.w3.eth.send_raw_transaction(signed_txn.rawTransaction) finally: logger.debug(f"nonce: {tx_params['nonce']}") + logger.debug(f"transaction: {transaction}") self.last_nonce = Nonce(tx_params["nonce"] + 1) def _get_tx_params(
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: