├── .env.example ├── .gitignore ├── hardhat.config.js ├── scripts └── deploySingleSwap.js ├── package.json ├── README.md └── contracts └── SingleSwap.sol /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | INFURA_GOERLI_ENDPOINT= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | require("dotenv").config(); 3 | 4 | /** @type import('hardhat/config').HardhatUserConfig */ 5 | module.exports = { 6 | solidity: "0.7.6", 7 | networks: { 8 | goerli: { 9 | url: process.env.INFURA_GOERLI_ENDPOINT, 10 | accounts: [process.env.PRIVATE_KEY], 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /scripts/deploySingleSwap.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | 3 | async function main() { 4 | console.log("deploying..."); 5 | const SingleSwap = await hre.ethers.getContractFactory("SingleSwap"); 6 | const singleSwap = await SingleSwap.deploy(); 7 | 8 | await singleSwap.deployed(); 9 | 10 | console.log("Single Swap contract deployed: ", singleSwap.address); 11 | } 12 | 13 | main().catch((error) => { 14 | console.error(error); 15 | process.exitCode = 1; 16 | }); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@ethersproject/abi": "^5.7.0", 5 | "@ethersproject/providers": "^5.7.1", 6 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", 7 | "@nomicfoundation/hardhat-network-helpers": "^1.0.6", 8 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 9 | "@nomiclabs/hardhat-ethers": "^2.1.1", 10 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 11 | "@typechain/ethers-v5": "^10.1.0", 12 | "@typechain/hardhat": "^6.1.3", 13 | "chai": "^4.3.6", 14 | "ethers": "^5.7.1", 15 | "hardhat": "^2.11.2", 16 | "hardhat-gas-reporter": "^1.0.9", 17 | "solidity-coverage": "^0.8.2", 18 | "typechain": "^8.1.0" 19 | }, 20 | "dependencies": { 21 | "@uniswap/v3-core": "^1.0.1", 22 | "@uniswap/v3-periphery": "^1.4.2", 23 | "dotenv": "^16.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uniswap Tutorials 2 | 3 | Create a smart contract that implements a simple token swap using the Uniswap V3 protocol. This DeFi tutorial explores the Uniswap V3 ISwapRouter interface for executing token swaps from Uinswap liquidity pools. Follow along and learn about this important DeFi primitive. 4 | 5 | ### YouTube tutorial video for Uniswap Token Swap tutorial: 6 | https://www.youtube.com/watch?v=GwMyv7CmoRs 7 | 8 | ### LINK (Goerli): 9 | 0x326C977E6efc84E512bB9C30f76E30c160eD06FB 10 | 11 | ### WETH (Goerli): 12 | 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6 13 | 14 | ### USDC (Goerli): 15 | 0x07865c6E87B9F70255377e024ace6630C1Eaa37F 16 | 17 | ### Uniswap SwapRouter address (Goerli): 18 | 0xE592427A0AEce92De3Edee1F18E0157C05861564 19 | 20 | ### SingleSwap.sol deployed (Goerli): 21 | 0xBdfCf1e48Fb8E7C66Fd7E0541BF9b1cf67558c49 22 | 23 | ### Deploy contract: 24 | npx hardhat run --network goerli scripts/deploySingleSwap.js 25 | 26 | #### GitHub direct link for Remix: 27 | import "https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol"; 28 | -------------------------------------------------------------------------------- /contracts/SingleSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity =0.7.6; 3 | pragma abicoder v2; 4 | 5 | import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; 6 | 7 | interface IERC20 { 8 | function balanceOf(address account) external view returns (uint256); 9 | 10 | function transfer(address recipient, uint256 amount) 11 | external 12 | returns (bool); 13 | 14 | function approve(address spender, uint256 amount) external returns (bool); 15 | } 16 | 17 | contract SingleSwap { 18 | address public constant routerAddress = 19 | 0xE592427A0AEce92De3Edee1F18E0157C05861564; 20 | ISwapRouter public immutable swapRouter = ISwapRouter(routerAddress); 21 | 22 | address public constant LINK = 0x326C977E6efc84E512bB9C30f76E30c160eD06FB; 23 | address public constant WETH = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; 24 | 25 | IERC20 public linkToken = IERC20(LINK); 26 | 27 | // For this example, we will set the pool fee to 0.3%. 28 | uint24 public constant poolFee = 3000; 29 | 30 | constructor() {} 31 | 32 | function swapExactInputSingle(uint256 amountIn) 33 | external 34 | returns (uint256 amountOut) 35 | { 36 | linkToken.approve(address(swapRouter), amountIn); 37 | 38 | ISwapRouter.ExactInputSingleParams memory params = ISwapRouter 39 | .ExactInputSingleParams({ 40 | tokenIn: LINK, 41 | tokenOut: WETH, 42 | fee: poolFee, 43 | recipient: address(this), 44 | deadline: block.timestamp, 45 | amountIn: amountIn, 46 | amountOutMinimum: 0, 47 | sqrtPriceLimitX96: 0 48 | }); 49 | 50 | amountOut = swapRouter.exactInputSingle(params); 51 | } 52 | 53 | function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) 54 | external 55 | returns (uint256 amountIn) 56 | { 57 | linkToken.approve(address(swapRouter), amountInMaximum); 58 | 59 | ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter 60 | .ExactOutputSingleParams({ 61 | tokenIn: LINK, 62 | tokenOut: WETH, 63 | fee: poolFee, 64 | recipient: address(this), 65 | deadline: block.timestamp, 66 | amountOut: amountOut, 67 | amountInMaximum: amountInMaximum, 68 | sqrtPriceLimitX96: 0 69 | }); 70 | 71 | amountIn = swapRouter.exactOutputSingle(params); 72 | 73 | if (amountIn < amountInMaximum) { 74 | linkToken.approve(address(swapRouter), 0); 75 | linkToken.transfer(address(this), amountInMaximum - amountIn); 76 | } 77 | } 78 | } 79 | --------------------------------------------------------------------------------