Note: For Uniswap 3 check out the tutorial here.
What is UniSwap?
If you're not familiar with Uniswap yet, it's a fully decentralized protocol for automated liquidity provision on Ethereum. An easier-to-understand description would be that it's a decentralized exchange (DEX) relying on external liquidity providers that can add tokens to smart contract pools and users can trade those directly.
Since it's running on Ethereum, what we can trade are Ethereum ERC-20 tokens. Each pool represents a pair of tokens; in v3 a pair can also have pools at different fee tiers. Uniswap - being fully decentralized - has no restrictions to which tokens can be added. If no contracts for a token pair exist yet, anyone can create one using their factory and anyone can provide liquidity to a pool. The swap fee and any protocol share depend on the protocol version and pool configuration; see the current fee documentation.
The price of a token is determined by the liquidity in a pool. For example if a user is buying TOKEN1 with TOKEN2, the supply of TOKEN1 in the pool will decrease while the supply of TOKEN2 will increase and the price of TOKEN1 will increase. Likewise, if a user is selling TOKEN1, the price of TOKEN1 will decrease. Therefore the token price always reflects the supply and demand.
And of course a user doesn't have to be a person, it can be a smart contract. That allows us to add Uniswap to our own contracts for adding additional payment options for users of our contracts. Uniswap makes this process very convenient, see below for how to integrate it.

What is new in UniSwap v2?
- ERC20 / ERC20 Pairs: In the first version any token had to be paired with ETH. To trade a token with another token, one had to first exchange the first token into ETH and then use that ETH to purchase the other token. Now you can directly trade those!
- Price Oracles: While theoretically one could use Uniswap v1 as an oracle, it wasn't recommended as prices could flucuate by a lot in a short time making it easy to manipulate. Now there are several mechanisms to prevent this like using the last block price and a cumulative-price that is weighted by the time previous prices existed.
- Flash Swaps: The Uniswap Flash Swaps are similar to the Aave Flash Loans that you might be familiar with. The same concept is now possible in Uniswap allowing you to optimistically receive tokens as long as you either 1. return them 2. pay for them or 3. partially return/pay for them at the end of the transaction.
- Some Minor Technical Improvements
- Path to Sustainability: The v2 protocol fee takes a share of the existing 0.30% swap fee, rather than adding another fee on top. Current documentation reports 0.25% for LPs and 0.05% for the protocol; the original non-activation plan below is no longer current.
Further Uniswap v2 resources
What happens to UniSwap v1?
Integrating UniSwap v2
One of the reasons Uniswap is so popular may be the simple way of integrating them into your own smart contract. Let's say you have a system where users pay with DAI. With Uniswap in just a few lines of code, you could add the option for them to also pay in ETH. The ETH can be automatically converted into DAI before the actual logic. It would look something like this
function pay(uint paymentAmountInDai, uint deadline) public payable {
if (msg.value > 0) {
convertEthToDai(paymentAmountInDai, deadline);
} else {
require(daiToken.transferFrom(msg.sender, address(this), paymentAmountInDai));
}
// do something with that DAI
...
}A simple check at the beginning of your function will be enough. Now as for the convertEthToDai function, it will look like something this:
function convertEthToDai(uint daiAmount, uint deadline) public payable {
address[] memory path = new address[](2);
path[0] = uniswapRouter.WETH();
path[1] = address(daiToken);
uint[] memory amounts = uniswapRouter.swapETHForExactTokens{value: msg.value}(daiAmount, path, address(this), deadline);
// refund leftover ETH to user
uint refund = msg.value - amounts[0];
(bool success,) = msg.sender.call{value: refund}("");
require(success, "Refund failed");
}There are several things to unpack here.
- Uniswap Router: The
uniswapRouterwill be a wrapper contract provided by Uniswap that has several safety mechanisms and convenience functions. Currently it is recommended to use the Router02 contract. You can instantiate it usingIUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D) only on a chain where that exact Router02 deployment is documented. The interface code can be found here. - Path: Any swap needs to have a starting and end path. While in Uniswap v2 you can have direct token to token pairs, it is not always guaranteed that such a pair actually exists. But you may still be able to trade them as long as you can find a path, e.g.,
Token1 → Token2 → WETH → Token3. In that case you can still trade Token1 for Token3, it will only cost a little bit more than a direct swap. - WETH: You might notice that we are using WETH here. In Uniswap v2 there are no more direct ETH pairs, all ETH must be converted to WETH first. In our case this is done by the router.
swapETHForExactTokens: This function can be used to use ETH and receive and exact amount of tokens for it. Any leftover ETH will be refunded, so make sure you have a fallback function in your contract to receive ETH:receive() payable external {}. Thedeadlineparameter will ensure that miners cannot withhold a swap and use it at a later, more profitable time. Make sure to pass this UNIX timestamp from your frontend, don't usenowinside the contract.- Refund: Once the trade is finished, we can return any leftover ETH to the user. Use the swap’s returned input amount to refund only this caller’s unused ETH; do not send the contract’s entire existing balance.
How to use it in the frontend
One issue we have now is when a user calls the pay function and wants to pay in ETH, we don't know how much ETH he needs. We can use the getAmountsIn function to compute exactly that.
function getEstimatedETHforDAI(uint daiAmount) public view returns (uint[] memory) {
address[] memory path = new address[](2);
path[0] = uniswapRouter.WETH();
path[1] = multiDaiKovan;
return uniswapRouter.getAmountsIn(daiAmount, path);
}Now we can call getEstimatedETHforDAI in our frontend. To ensure we are sending enough ETH and that the transaction won't get reverted, we can increase the estimated amount of ETH by a little bit:
// ethers v6; example 10% input cap, choose a deliberate slippage policy.
const requiredEth = (await myContract.getEstimatedETHforDAI(daiAmount))[0];
const sendEth = (requiredEth * 110n + 99n) / 100n;
Historical Remix example and compatibility limits
This is the original Solidity 0.7.1 Router02 example with historical Kovan addresses. Kovan is retired. Reproduction requires an isolated, correctly configured historical environment; a current deployment needs verified chain-specific addresses, a caller-supplied deadline and an explicit price/slippage policy.
pragma solidity 0.7.1;
import "https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol";
contract UniswapExample {
address internal constant UNISWAP_ROUTER_ADDRESS = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D ;
IUniswapV2Router02 public uniswapRouter;
address private multiDaiKovan = 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa;
constructor() {
uniswapRouter = IUniswapV2Router02(UNISWAP_ROUTER_ADDRESS);
}
function convertEthToDai(uint daiAmount, uint deadline) public payable {
uint[] memory amounts = uniswapRouter.swapETHForExactTokens{ value: msg.value }(daiAmount, getPathForETHtoDAI(), address(this), deadline);
// refund leftover ETH to user
(bool success,) = msg.sender.call{ value: msg.value - amounts[0] }("");
require(success, "refund failed");
}
function getEstimatedETHforDAI(uint daiAmount) public view returns (uint[] memory) {
return uniswapRouter.getAmountsIn(daiAmount, getPathForETHtoDAI());
}
function getPathForETHtoDAI() private view returns (address[] memory) {
address[] memory path = new address[](2);
path[0] = uniswapRouter.WETH();
path[1] = multiDaiKovan;
return path;
}
// important to receive ETH
receive() payable external {}
}



Join the conversation
Comments are hosted by Disqus and load only when you choose to enable them.