Set Up Your Local Environment
Set up a local Uniswap v3 smart contract development environment with Node.js, npm, and Hardhat.
One of the most common questions is what development toolset to use to build onchain integrations with Uniswap. There's no right answer to this question, but this guide uses Node.js, NPM, and Hardhat.
At the end of this guide you’ll have a development environment set up that you can use to build the rest of the examples in the Guides section of the docs, or start your own integration project!
To get started as quickly as possible, use the Getting Started section below where you can clone a boilerplate and start building. To start from scratch and learn the underlying concepts, jump to the Start from Scratch section.
Getting Started
The Uniswap boilerplate repo provides a basic Hardhat environment with required imports already pre-loaded for you. You can simply clone it and install the dependencies:
git clone https://github.com/Uniswap/uniswap-first-contract-example
cd uniswap-first-contract-example
npm installThen hop to the Local Node with a Mainnet Fork to complete your set up and start developing.
Start from Scratch
In the following sections, we’ll walk through the steps to create the same environment set up as the boiler plate from scratch and learn the underlying concepts.
Set up dependencies
Node is one of the most common Javascript runtimes. For our purposes it will provide scripting we can use to compile and test our contracts. If you haven’t already, install NodeJS and its package manager NPM (instructions). Once those dependencies are set up, we can initialize our project:
$ npm initHardhat is an Ethereum development toolset that provides a number of powerful features including Solidity compilation, testing and deployment, all in a single convenient wrapper. We’ll use NPM to add Hardhat to our project:
$ npm add --save-dev hardhatWith Hardhat installed we can invoke it to scaffold our development environment. When you first run Hardhat you’ll have the option of starting with a templated Javascript or Typescript project or an empty project. Since Hardhat relies heavily on folder structure, we recommend starting with either of the templated options. Initialize Hardhat and follow the prompts to make your selection and answer yes to the follow up prompts:
$ npx hardhat initOnce the Hardhat initialization completes, take a look around at what got set up. The folder structure should be intuitive, ./contracts is where you’ll write your Solidity contracts, ./test is where you’ll write your tests and ./scripts is where you can write scripts to perform actions like deploying. Out of the box, Hardhat is configured to use this folder structure so don’t change it unless you know what you’re doing!
Next we’ll use NPM to add the Uniswap v3 contracts which will allow us to seamlessly integrate with the protocol in our new contracts:
$ npm add @uniswap/v3-periphery @uniswap/v3-coreThe Uniswap v3 contracts were written using a past version of the solidity compiler. Since we’re building integrations on v3 we have to tell Hardhat to use the correct compiler to build these files. Go to the ./hardhat.config.js file and change the Solidity version to “0.7.6”:
// ...
module.exports = {
solidity: "0.7.6",
};That’s it! You should now have a functional development environment to start building onchain Uniswap integrations. Let’s run a quick test to confirm everything is set up properly.
Compile a Basic Contract
To confirm that our environment is configured correctly we’ll attempt to compile a basic Swap contract. Create a new file, ./contracts/Swap.sol and paste the following code into it (a detailed guide to this contract can be found here:
// 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/v3-periphery/contracts/libraries/TransferHelper.sol';
contract SimpleSwap {
ISwapRouter public immutable swapRouter;
address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
uint24 public constant feeTier = 3000;
constructor(ISwapRouter _swapRouter) {
swapRouter = _swapRouter;
}
function swapWETHForDAI(uint256 amountIn) external returns (uint256 amountOut) {
// Transfer the specified amount of WETH9 to this contract.
TransferHelper.safeTransferFrom(WETH9, msg.sender, address(this), amountIn);
// Approve the router to spend WETH9.
TransferHelper.safeApprove(WETH9, address(swapRouter), amountIn);
// Note: To use this example, you should explicitly set slippage limits, omitting for simplicity
uint256 minOut = /* Calculate min output */ 0;
uint160 priceLimit = /* Calculate price limit */ 0;
// Create the params that will be used to execute the swap
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: WETH9,
tokenOut: DAI,
fee: feeTier,
recipient: msg.sender,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: minOut,
sqrtPriceLimitX96: priceLimit
});
// The call to `exactInputSingle` executes the swap.
amountOut = swapRouter.exactInputSingle(params);
}
}To compile all the contracts in the ./contracts folder, we’ll use the Hardhat compile command:
$ npx hardhat compileIf the environment is compiled correctly you should see the message:
Compiled { x } Solidity files successfullyLocal Node with a Mainnet Fork
When building and testing integrations with onchain protocols, developers often hit a problem: the liquidity on the live chain is critical to thoroughly testing their code but testing against a live network like Mainnet can be extremely expensive.
See the SDK getting started guide for a full example on how to use forks.
With your local node up and running, you can use the --network localhost flag in tests to point the Hardhat testing suite to that local node:
$ npx hardhat test --network localhost