Use the new Uniswap v3 in your contracts (2023)

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.

Use the new Uniswap v3 in your contracts (1)

We discussed what's new in Uniswap v2here, but now let's see what's new in Uniswap v3:

(Video) Uniswap V3 Tutorial | Uniswap Smart Contract (Single Swap) | DeFi Tutorial
  • 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.

(Video) Developer walks you through the code of Uniswap V3

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 thoseconvertirEthToExactDaifunction 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 withISwapRouter(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 calluniswapRouter.reembolsoETH()after an exchange! And make sure you have an alternative function in your contract to get ETH:Receive() externally payable {}. ThatexpressionThe 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 usenowwithin 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.

(Video) Uniswap V3 Factory Contract Deep Dive | Solidity Contract Code Walk Through

now we can callGetETHestimatedforDAIin 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 aAwayas 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()}
(Video) Liquidity Uniswap V3 Solidity Smart Contract | How to Provide Liquidity ERC20 Token Uniswap Exchange

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.

Use the new Uniswap v3 in your contracts (2)

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.

Use the new Uniswap v3 in your contracts (3)

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. ;)

Use the new Uniswap v3 in your contracts (4)

(Video) Liquidity Pool Smart Contract Testing | Testing Uniswap V3 Periphery Contract Using Hardhat Locally

Videos

1. Best Way To Earn ETH Flash Loan Arbitrage Tutorial Uniswap Updated on Metamask 2022 Crypto Passively
(Freesa Crypto Investors)
2. This SECRET Uniswap v3 Strategy Will Make MILLIONAIRES! (DeFi Passive Income)
(Jake Crypto)
3. Locally Deploy Uniswap V3 Contracts and Pool with Hardhat | Solidity & JavaScript | Coding Tutorial
(Blockman Codes)
4. Uniswap V3 - Add Liquidity - Mint New Position | DeFi
(Smart Contract Programmer)
5. Uniswap V3 - Increase Liquidity | DeFi
(Smart Contract Programmer)
6. Uniswap V3 Position Contract Deep Dive | Solidity Contract Code Walk Through | Position.sol Library
(Blockman Codes)

References

Top Articles
Latest Posts
Article information

Author: Manual Maggio

Last Updated: 10/13/2023

Views: 6097

Rating: 4.9 / 5 (49 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Manual Maggio

Birthday: 1998-01-20

Address: 359 Kelvin Stream, Lake Eldonview, MT 33517-1242

Phone: +577037762465

Job: Product Hospitality Supervisor

Hobby: Gardening, Web surfing, Video gaming, Amateur radio, Flag Football, Reading, Table tennis

Introduction: My name is Manual Maggio, I am a thankful, tender, adventurous, delightful, fantastic, proud, graceful person who loves writing and wants to share my knowledge and understanding with you.