If you are not familiarUniswapHowever, it is a fully decentralized protocol for the automated delivery of liquidity on Ethereum. An easier to understand description would be that it is a decentralized exchange (DEX) that relies on external liquidity providers who can add tokens to pools of smart contracts and allow users to trade them directly.
Since it runs on Ethereum, we can exchange Ethereum ERC-20 tokens. Each token has its own smart contract and liquidity pool. Because Uniswap is fully decentralized, there are no restrictions on what tokens can be added to. If no token pair contracts exist yet, anyone can create one with your factory and anyone can provide liquidity to a pool. As an incentive, these liquidity providers receive a fee of 0.3% per trade.
The price of a token is determined by the liquidity in a pool. For example when a user makes a purchaseTOKEN1conTOKEN2, the delivery ofTOKEN1the supply in the pool will decreaseTOKEN2will rise and the price ofTOKEN1will rise. Likewise when a user sellsTOKEN1, the price ofTOKEN1it will decrease. Therefore, the token price always reflects supply and demand.
And of course, a user doesn't have to be a person, it can be a smart contract. This allows us to add Uniswap to our own contracts to add additional payment options for users of our contracts. Uniswap makes this process very convenient, see below how to integrate it.
We discussed what's new in Uniswap v2here, but now let's see what's new in Uniswap v3:
A new functionality for liquidity providers that allows them to define a valid price range. As long as the pool is currently out of range, its liquidity is ignored. Not only does this reduce the risk of non-permanent losses for liquidity providers, but it is also far more efficient from a capital perspective as...
- Different rate levels determined by the risk level of the group. There are three different levels:
- stable pairs: 0.05%. These fees are said to apply to pairs that have a low risk of volatility such as USDT/DAI. Since both are stablecoins, the potential temporary loss from them is very small. This is particularly interesting for traders as it will allow for very cheap trades between stablecoins.
- Medium risk couples: 0.30%. Medium risk pairs are considered unrelated pairs that have high trading volume/popularity. Popular pairs tend to have a slightly lower risk of volatility.
- high-risk colleagues: 1.00%. All other exotic pairs are considered high risk for liquidity providers and are subject to the higher 1% trading fee.
Uniswap v2 Oracle TWAP mechanism has been improved, where a single chain call can get the TWAP price of the last 9 days. To achieve this, all relevant prices are stored in a fixed-size array, instead of just storing a cumulative sum of prices. This will arguably increase your gas costs slightly, but overall it's worth it for the greatly improved Oracle.
More Uniswap v3 resources
Uniswap is an automated and decentralized suite of smart contracts. It will work as long as Ethereum exists.
Hayden Adams
Integrando UniSwap v3
One of the reasons why Uniswap is so popular might be the easy way to integrate them into your own smart contract. Suppose you have a system where users pay with DAI. With Uniswap, you can add the option to also pay in ETH in just a few lines of code. ETH can be automatically converted to DAI before the actual logic. It would look something like this
Funktion pay(uint AmountOfPaymentInDai) public pay { if (message.value > 0) { convertEthToExactDai (AmountOfPaymentInDai); } else { require(daiToken.transferFrom(msg.sender, address(this), paymentAmountInDai); } // mache etwas mit dieser DAI ...}
A simple check at the beginning of your function is sufficient. Now to thoseconvertirEthToExactDai
function looks like this:
function convertEthToExactDai(uint256 daiAmount) external payment { require(daiAmount > 0, "You must pass a non-zero DAI amount"); require(msg.value > 0, "You must pass a non-zero ETH amount"); Deadline uint256 = block.timestamp + 15; // use 'now' for convenience to get the mainnet pass deadline from the frontend! address tokenIn = WETH9; TokenOut address = multiDaiKovan; rate uint24 = 3000; Recipient address = message.sender; uint256 AmountOut = Amountdai; uint256 AmountInMaximum = msg.value; uint160 sqrtPriceLimitX96 = 0; ISwapRouter.ExactOutputSingleParams storage parameters = ISwapRouter.ExactOutputSingleParams(tokenIn, tokenOut, rate, recipient, date, output quantity, maximum quantity, sqrtPriceLimitX96); uniswapRouter.exactOutputSingle{ value: message.value }(parameters); uniswapRouter.refundETH(); // Refund excess ETH to user (bool success) = msg.sender.call{ value: address(this).balance }(""); require(success, "refund failed");}
There is a lot to unpack here.
- swap routers: Thatswap routersIt will be a wrapper contract provided by Uniswap with various security mechanisms and convenience features. You can instantiate it with
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564
) for each main or test network. The interface code can be foundhere. - WETH: You might notice that we use ETH here. There are no more direct ETH pairs on Uniswap, all ETH must first be converted to WETH (which is ETH packaged as ERC-20). In our case, the router does this.
- ExactOutputSingle:This feature can be used to use ETH and get an exact amount of tokens for it. Any remaining ETH will be refunded, howevernot automatically! I didn't realize that at first and ETH ended up in the exchange router contract. In order toDon't forget to call
uniswapRouter.reembolsoETH()
after an exchange! And make sure you have an alternative function in your contract to get ETH:Receive() externally payable {}
. Thatexpression
The parameter ensures that miners are not stuck on one exchange and can later use it at a more profitable time. Make sure you pass this UNIX timestamp from your interface,don't usenow
within the contract. - refund: Once the operation is complete, we can return any remaining ETH to the user. This will send all ETH in the contract. So if your contract has an ETH balance for other reasons, make sure you change that.
- Rate: It's not a stable but popular pair, so the fee we use here is 0.3% (see Fees section above).
sqrtPriceLimitX96: Can be used to set pool price limits that swap cannot exceed. If you set it to 0, it will be ignored.
How to use it in UI
One problem we have now is that when a user enters the payment function and wants to pay in ETH, we don't know how much ETH they need. We can use thosequoteExactOutputSinglefunction to calculate just that.
function getEstimatedETHforDAI (uint daiAmount) externalPayableReturns (uint256) { address tokenIn = WETH9; Address tokenOut = multiDaiKovan; rate uint24 = 500; uint160 sqrtPriceLimitX96 = 0; Return quoter.quoteExactOutputSingle(tokenIn, tokenOut, fee, daiAmount, sqrtPriceLimitX96); } }
However, note that we are not declaring this as a view function, we areDo not call this function in the chain. It's still supposed to be called as a view function, but since you're using non-view functions under the hood to calculate the result, it's not possible to declare it as a view function yourself (Solidity? ). For example, use Web3'sPhone call()Functionality to read the result in the interface.
now we can callGetETHestimatedforDAI
in our interface. To make sure we send enough ETH and the transaction doesn't get rolled back, we can increase the estimated ETH amount a bit:
const requiredEth = (esperar myContract.getEstimatedETHforDAI(daiAmount).call())[0];const sendEth = requiredEth * 1.1;
What if no direct pool is available for a trade?
In this case you can use theexact inputjexact outputfunctions that take aAway
as parameters. This path is byte-encoded data (encoded for gas efficiency) of the token addresses.
Each exchange must have a start and end path. While you may have direct token-to-token pairs on Uniswap, such a pool is not always guaranteed to actually exist. You can still swap them out though, as long as you find a path, e.g. B. Token1 → Token2 → WETH → Token3. In this case, you can still swap Token1 for Token3, it just costs a little more gas than a direct swap.
You can see them on the rightUniswap sample codehow to calculate this path in the interface.
function encodePath(tokenAddresses, fee) { const FEE_SIZE = 3 if (path.length != fares.length + 1) { throw new Error('path/fee length does not match') } let encoded = '0x' for (let i = 0; i < fee.length; i++) { // 20-Byte-Kodierung der verschlüsselten Adresse += route[i].slice(2) // 3-Byte-Kodierung der verschlüsselten Gebühr += Gebühr[ i] .toString (16).padStart(2 * FEE_SIZE, '0') } // encode final encoded token += path[path.length - 1].slice(2) return encoded.toLowerCase()}
Here's a fully working example that you can use directly in Remix. It allows you to exchange ETH forMulticollateralized Kovan DAI. It also includes the alternative toExactOutputSinglewhichentradaexactaSingleand allows you to trade ETH for the amount of DAI you get in return.
// SPDX license identifier: MITpragma solidity =0.7.6;pragma acoder v2;import "https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol"; import "https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/IQuoter.sol"; the IUniswapRouter interface is ISwapRouter { function refundETH() externally payable; } Uniswap3 contract { IUniswapRouter public constant UniswapRouter = IUniswapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); IQuoter public constant quoter = IQuoter(0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6); private address constant multiDaiKovan = 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa; private address constant WETH9 = 0xd0A1E359811322d97991E03f863a0C30C2cF029C; function convertExactEthToDai() externally payable {require(msg.value > 0, "You must pass a non-zero ETH amount"); Deadline uint256 = block.timestamp + 15; // use 'now' for convenience to get the mainnet pass deadline from the frontend! address tokenIn = WETH9; TokenOut address = multiDaiKovan; rate uint24 = 3000; Recipient address = message.sender; uint256 amountIn = message.value; uint256 AmountOutMinimum = 1; uint160 sqrtPriceLimitX96 = 0; ISwapRouter.ExactInputSingleParams storage parameters = ISwapRouter.ExactInputSingleParams(tokenIn, tokenOut, fee, recipient, date, QuantityIn, QuantityOutMinimum, sqrtPriceLimitX96); uniswapRouter.exactInputSingle{ value: msg.value } (parameter); uniswapRouter.refundETH(); // Refund excess ETH to user (bool success) = msg.sender.call{ value: address(this).balance }(""); require(success, "Refund failed"); } function convertEthToExactDai(uint256 daiAmount) external payment { require(daiAmount > 0, "You must pass a non-zero DAI amount"); require(msg.value > 0, "You must pass a non-zero ETH amount"); Deadline uint256 = block.timestamp + 15; // use 'now' for convenience to get the mainnet pass deadline from the frontend! address tokenIn = WETH9; TokenOut address = multiDaiKovan; rate uint24 = 3000; Recipient address = message.sender; uint256 AmountOut = Amountdai; uint256 AmountInMaximum = msg.value; uint160 sqrtPriceLimitX96 = 0; ISwapRouter.ExactOutputSingleParams storage parameters = ISwapRouter.ExactOutputSingleParams(tokenIn, tokenOut, rate, recipient, date, output quantity, maximum quantity, sqrtPriceLimitX96); uniswapRouter.exactOutputSingle{ value: message.value }(parameters); uniswapRouter.refundETH(); // Refund excess ETH to user (bool success) = msg.sender.call{ value: address(this).balance }(""); require(success, "Refund failed"); } // not used in the chain, gas is inefficient! function getEstimatedETHforDAI(uint daiAmount) external payable returns(uint256) { tokenIn address = WETH9; TokenOut address = multiDaiKovan; rate uint24 = 3000; uint160 sqrtPriceLimitX96 = 0; return quote.quoteExactOutputSingle(tokenIn, tokenOut, fee, daiAmount, sqrtPriceLimitX96); } // important to get ETH Receive() payable external {}}
The difference between exact input and exact output
Once you run the features and check them out on Etherscan, the difference is immediately apparent. Here we work with the exact power. We provide 1 ETH and would like to receive 100 DAI in return. Excess ETH will be refunded to us.
And here we are trading with the exact input. We provide 1 ETH and want to get as much DAI as possible, which happens to be 196 DAI.
Please note that if you are confused as to why the price is so different, it is a small pool on the testnet and the first trade impacted the pool price a lot. Not many people trade on a testnet. ;)