Uniswap V3 - Flash Swap

Search for a command to run...

No comments yet. Be the first to comment.
In this series, I will explain on how to interact with the Uniswap V3. a famous DEX protocol in the Ethereum ecosystem in our solidity smart contracts.
We all know that Uniswap has become the most used DEX on the market and it has wide variety of features. The Uniswap v3 has many interesting features from v1 and v2. And in this guide, we will understand the technicalities on how to interact with the...
All about perpetual contracts

A Technical Guide for Beginners to Ethereum's Proof of Stake

Transaction and type of Transactions in the Ethereum blockchain

Structure of an Ethereum Node

Update to ERC1155

Hello everyone.
Welcome to another day of understanding the Web3 World.
So far, in this series, we are trying to understand the Uniswap V3 and how to integrate it into our smart contracts. In today's blog, let us see how to perform flash swaps in our contracts with V3.
Uniswap Flash swap allows you to borrow tokens from any pool with no upfront cost for a block time and then able to perform your logic such as arbitrage etc., with a small amount of fee for the loan amount. In other words, you can borrow tokens with small fee, do your thing and return the tokens including the fee in the same transaction.
Typically the flash swaps are used to perform arbitrage and earn profits. The process involves 2 transactions from the user. The approval of the fee tokens and the invoke flash call. Check the below code to understand it better.
contract FlashSwap is IUniswapV3FlashCallback {
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
using SafeERC20 for IERC20;
IUniswapV3Pool public pool;
IERC20 public token0;
IERC20 public token1;
constructor(IUniswapV3Pool _pool) {
pool = _pool;
token0 = IERC20(pool.token0());
token1 = IERC20(pool.token1());
}
function uniswapV3FlashCallback(
uint256 fee0,
uint256 fee1,
bytes calldata data
) external override {
require(msg.sender == address(pool), "not authorized");
FlashCallbackData memory decoded = abi.decode(
data,
(FlashCallbackData)
);
// write custom code here in this block
uint token0Bal = token0.balanceOf(address(this));
console.log("Contract - Token 0 Balance: ", token0Bal / 1e18);
uint token1Bal = token1.balanceOf(address(this));
console.log("Contract - Token 1 Balance: ", token1Bal / 1e6);
// payback to the pool
uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0);
uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1);
// collect fee from the user
if (fee0 > 0)
token0.safeTransferFrom(decoded.payer, address(this), fee0);
if (fee1 > 0)
token1.safeTransferFrom(decoded.payer, address(this), fee1);
if (amount0Owed > 0) token0.safeTransfer(address(pool), amount0Owed);
if (amount1Owed > 0) token1.safeTransfer(address(pool), amount1Owed);
}
struct FlashCallbackData {
uint256 amount0;
uint256 amount1;
address payer;
}
function initFlash(uint amount0, uint amount1) external {
bytes memory data = abi.encode(
FlashCallbackData({
amount0: amount0,
amount1: amount1,
payer: msg.sender
})
);
pool.flash(address(this), amount0, amount1, data);
}
}
Full contract is available here: https://github.com/jveer634/uniswap-v3-demo/blob/master/contracts/FlashSwap.sol
In the above contract, we have 2 functions.
initFlash - Called by the user to start the process. The function will call the flash method on the pool, with 4 parameters.
recipient - address of the recipient of the borrowed tokens
amount0 - amount of token0 to borrow
amount1 - amount of token1 to borrow
data - optional data to be passed to the flash callback function.
uniswapV3FlashCallback - The callback function invoked by the pool, once the tokens are transferred to the caller. The name of the shouldn't be changed, and it is provided by the interface IUniswapV3FlashCallback. This function should have 3 arguments
fee0 - amount of fee for borrowed token0
fee1 - amount of fee for borrowed token1
data - the optional data passed in the initFlash function
In the callback function, we are first restricting the access to only pool and then we are decoding the data using our FlashCallbackData struct. Once that is done, we can perform our logic there.
For this demo, I am logging the balances of our contract using the console.sol contract provided the hardhat framework.
After out task, then we are returning the tokens received, along with the fees for the loan. Below we have the script for testing our contract.
import { ethers } from "hardhat";
import { FlashSwap, IERC20 } from "../typechain-types";
describe("FlashSwap", () => {
let flash: FlashSwap, dai: IERC20, user: string;
const DAI_WHALE = "0xe5F8086DAc91E039b1400febF0aB33ba3487F29A";
const DAI_TOKEN = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const POOL = "0xa63b490aa077f541c9d64bfc1cc0db2a752157b5";
const DAI_AMOUNT = ethers.parseUnits("0.03", 18);
beforeEach(async () => {
const daiSigner = await ethers.getImpersonatedSigner(DAI_WHALE);
const signers = await ethers.getSigners();
await signers[0].sendTransaction({
value: ethers.parseEther("5"),
to: DAI_WHALE,
});
dai = await ethers.getContractAt("IERC20", DAI_TOKEN);
// transfer tokens to our signer to make transactions
user = signers[0].address;
await dai.connect(daiSigner).transfer(user, DAI_AMOUNT);
const s = await ethers.deployContract("FlashSwap", [POOL]);
flash = await s.waitForDeployment();
});
it(".. test flashswap", async () => {
await dai.approve(flash.target, DAI_AMOUNT);
console.log(
"User - Before DAI balance: ",
ethers.formatUnits(await dai.balanceOf(user), 18)
);
await flash.initFlash(ethers.parseEther("10"), 0);
console.log(
"User - After DAI balance: ",
ethers.formatUnits(await dai.balanceOf(user), 18)
);
});
});
With that, we are done with integrating the Uniswap V3 features into our contracts. Stay tuned for more interesting Web3 topics.