Last active
October 28, 2024 23:48
-
-
Save claytantor/89cbc6f40d30a2a268cdfcc4c66f2cf0 to your computer and use it in GitHub Desktop.
Swap Example for V3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const hre = require("hardhat"); | |
| async function main() { | |
| console.log("Deploying SwapExampleV3 contract...",hre.ethers.version); | |
| // SwapRouter02 | |
| // mainnet 0x2626664c2603336E57B271c5C0b26F421741e481 | |
| // sepolia 0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4 | |
| // Get the contract factory and deploy the contract with the router address as a parameter | |
| const SwapExampleV3 = await hre.ethers.getContractFactory("SwapExampleV3"); | |
| const swapExample = await SwapExampleV3.deploy(); | |
| // Wait for the contract deployment to be mined | |
| await swapExample.deployed(); | |
| // Display the contract address | |
| console.log("Contract deployed to address:", swapExample.address); | |
| } | |
| // Run the script and handle errors | |
| main().catch((error) => { | |
| console.error(error); | |
| process.exitCode = 1; | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const ethers = require("ethers"); | |
| const dotenv = require("dotenv"); | |
| const hre = require("hardhat"); | |
| if (process.argv.length < 3) { | |
| console.error("Please provide the environment file path as an argument."); | |
| process.exit(1); | |
| } else { | |
| dotenv.config({ path: process.argv[2] }); | |
| } | |
| const { | |
| ETHERS_NETWORK, | |
| INFURA_PROJECT_ID, | |
| FROM_ADDRESS_PRIVATE_KEY, | |
| SWAP_CONTRACT_ADDRESS, | |
| } = process.env; | |
| async function main() { | |
| console.log("ethers:", ethers.version); | |
| console.log("ETHERS_NETWORK:", ETHERS_NETWORK); | |
| console.log("INFURA_PROJECT_ID:", INFURA_PROJECT_ID); | |
| // Set up the contract factory and provider | |
| const provider = new ethers.providers.InfuraProvider(ETHERS_NETWORK, INFURA_PROJECT_ID); | |
| const wallet = new ethers.Wallet(FROM_ADDRESS_PRIVATE_KEY, provider); | |
| const balance = await wallet.getBalance(); | |
| console.log("Current ETH balance:", ethers.utils.formatEther(balance)); | |
| // Load the contract | |
| const SwapExampleV3 = await hre.ethers.getContractFactory("SwapExampleV3", wallet); | |
| const swapExampleV3 = await SwapExampleV3.attach(SWAP_CONTRACT_ADDRESS); | |
| if (balance.lt(ethers.utils.parseUnits("0.05", "ether"))) { | |
| console.error("Insufficient ETH balance for gas. Please add funds to the wallet."); | |
| return; | |
| } | |
| // Call the swap function | |
| const amountIn = ethers.utils.parseUnits("3.0", 6); // Adjust based on the token's decimals | |
| console.log("amountIn:", amountIn); | |
| try { | |
| // Call the `swapExactInputSingle` function | |
| const gasLimitMax = 500000; | |
| const tx = await swapExampleV3.swapExactInputSingle(amountIn, { | |
| gasLimit: gasLimitMax // Adjust the value as needed | |
| }); | |
| console.log("Transaction hash:", tx.hash); | |
| // Wait for the transaction to be confirmed | |
| const receipt = await tx.wait(); | |
| console.log("Transaction confirmed in block:", receipt.blockNumber); | |
| // Log the amountOut from the event or receipt | |
| console.log("Transaction completed. Output received:", receipt); | |
| } catch (error) { | |
| console.error("Error during swap:", error); | |
| } | |
| } | |
| main().catch((error) => { | |
| console.error("Error in main:", error); | |
| process.exit(1); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: GPL-2.0-or-later | |
| pragma solidity = 0.7.6; | |
| pragma abicoder v2; | |
| // import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; | |
| import '@uniswap/swap-router-contracts/contracts/interfaces/ISwapRouter02.sol'; | |
| interface IERC20 { | |
| function balanceOf(address account) external view returns (uint256); | |
| function transfer(address recipient, uint256 amount) | |
| external | |
| returns (bool); | |
| function approve(address spender, uint256 amount) external returns (bool); | |
| } | |
| contract SwapExampleV3 { | |
| // For the scope of these swap examples, | |
| // we will detail the design considerations when using | |
| // `exactInput`, `exactInputSingle`, `exactOutput`, and `exactOutputSingle`. | |
| // It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity. | |
| // More advanced example contracts will detail how to inherit the swap router safely. | |
| // https://docs.uniswap.org/contracts/v3/reference/deployments/ethereum-deployments | |
| address public constant routerAddressMainnet = | |
| 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; | |
| address public constant routerAddressSepolia = | |
| 0x3bFA4769FB09eefC5a80d6E87c3B9C650f7Ae48E; | |
| ISwapRouter02 public immutable swapRouter = ISwapRouter02(routerAddressSepolia); | |
| // on Sepolia testnet | |
| address public constant USDC = 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238; | |
| address public constant WETH = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14; | |
| IERC20 public usdcToken = IERC20(USDC); | |
| // For this example, we will set the pool fee to 0.3%. | |
| uint24 public constant poolFee = 3000; | |
| // constructor(ISwapRouter _swapRouter) { | |
| // swapRouter = _swapRouter; | |
| // } | |
| constructor() {} | |
| /// @notice swapExactInputSingle swaps a fixed amount of USDC for a maximum possible amount of WETH | |
| /// using the USDC/WETH 0.3% pool by calling `exactInputSingle` in the swap router. | |
| /// @dev The calling address must approve this contract to spend at least `amountIn` worth of its USDC for this function to succeed. | |
| /// @param amountIn The exact amount of USDC that will be swapped for WETH. | |
| /// @return amountOut The amount of WETH received. | |
| function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) { | |
| usdcToken.approve(address(swapRouter), amountIn); | |
| // Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum. | |
| // We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount | |
| ISwapRouter02.ExactInputSingleParams memory params = | |
| ISwapRouter02.ExactInputSingleParams({ | |
| tokenIn: USDC, | |
| tokenOut: WETH, | |
| fee: poolFee, | |
| recipient: address(this), | |
| amountIn: amountIn, | |
| amountOutMinimum: 0, | |
| sqrtPriceLimitX96: 0 | |
| }); | |
| // The call to `exactInputSingle` executes the swap. | |
| amountOut = swapRouter.exactInputSingle(params); | |
| } | |
| /// @notice swapExactOutputSingle swaps a minimum possible amount of USDC for a fixed amount of WETH. | |
| /// @dev The calling address must approve this contract to spend its USDC for this function to succeed. As the amount of input USDC is variable, | |
| /// the calling address will need to approve for a slightly higher amount, anticipating some variance. | |
| /// @param amountOut The exact amount of WETH to receive from the swap. | |
| /// @param amountInMaximum The amount of USDC we are willing to spend to receive the specified amount of WETH. | |
| /// @return amountIn The amount of USDC actually spent in the swap. | |
| function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { | |
| // Transfer the specified amount of USDC to this contract. | |
| // TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amountInMaximum); | |
| // Approve the router to spend the specifed `amountInMaximum` of USDC. | |
| // In production, you should choose the maximum amount to spend based on oracles or other data sources to acheive a better swap. | |
| // TransferHelper.safeApprove(USDC, address(swapRouter), amountInMaximum); | |
| usdcToken.approve(address(swapRouter), amountInMaximum); | |
| ISwapRouter02.ExactInputSingle memory params = | |
| ISwapRouter02.ExactOutputSingleParams({ | |
| tokenIn: USDC, | |
| tokenOut: WETH, | |
| fee: poolFee, | |
| recipient: address(this), | |
| amountOut: amountOut, | |
| amountInMaximum: amountInMaximum, | |
| sqrtPriceLimitX96: 0 | |
| }); | |
| // Executes the swap returning the amountIn needed to spend to receive the desired amountOut. | |
| amountIn = swapRouter.exactOutputSingle(params); | |
| // // For exact output swaps, the amountInMaximum may not have all been spent. | |
| // // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0. | |
| if (amountIn < amountInMaximum) { | |
| usdcToken.approve(address(swapRouter), 0); | |
| usdcToken.transfer(address(this), amountInMaximum - amountIn); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment