Uniswap V3 - Flash Swap
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.
What is Flash Swap ?
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.
How can we do that ?
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.
Subscribe to my newsletter
Read articles from Jay Nalam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Jay Nalam
Jay Nalam
Hi, I'm Jay Nalam, a seasoned Web3 Engineer committed to advancing decentralized technologies. Specializing in EVM-based blockchains, smart contracts, and web3 protocols, I've developed NFTs, DeFi protocols, and more, pushing boundaries in the crypto realm.