Flash Swap
Prereq: Swap
This page covers the necessary concepts to understand flash swap flows in Uniswap V2.
With flash swap, you can make capital-free and collateral-free trades as long as you return your borrowed amount plus fees in the same tx.
Callback
Section titled “Callback”Swap is coded to support advanced/custom swap scenarios:
- Uniswap V2 treats swaps as “optimistic”: the pair transfers out amount0Out or amount1Out before checking that it has been repaid by the end of the same transaction.
- If your data.length == 0, it behaves like a normal swap,
- If data.length > 0, the pair triggers
uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data)on the to contract. - At the end the pair must
satisfy its reserve invariant plus fees, otherwise the whole transaction reverts
// contracts/uniswap-v2/v2-core/contracts/UniswapV2Pair.sol:158:187// this low-level function should be called from a contract which performs important safety checksfunction swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); }
_update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);}Callback steps
Section titled “Callback steps”The basic sequence is:
- Your contract calls the pair’s
swap(...)and asks fortoken0ortoken1out. - The pair
transfers the requested tokens to your contract - Then calls
uniswapV2Call(...). - Inside
uniswapV2Call(...)- you make some recommended
checks- fetch token the addresses
- check the pair existence
- you run your
custom logic, such as arbitrage, collateral swapping, or liquidation. - you
transfer back enough tokensso the pairsatisfies its reserve invariant plus fees
- you make some recommended
Minimum Validation
Section titled “Minimum Validation”function uniswapV2Call( address sender, uint amount0, uint amount1, bytes calldata) external { address token0 = IUniswapV2Pair(msg.sender).token0(); address token1 = IUniswapV2Pair(msg.sender).token1(); address pair = IUniswapV2Factory(factory).getPair(token0, token1);
require(msg.sender == pair, "INVALID_PAIR"); require(amount0 == 0 || amount1 == 0, "ONLY_ONE_TOKEN_OUT"); require(amount0 > 0 || amount1 > 0, "ZERO_BORROW");
// rest}Multi token swap
Section titled “Multi token swap”This is the case where the token withdrawn is different than the token returned.
Effective fee
Section titled “Effective fee”Ordinary Uniswap V2 swap with a 0.3% fee on the input/returned token amount.
No fees on output/withdrawn amount.
Example
Section titled “Example”function uniswapV2Call( address sender, uint amount0, uint amount1, bytes calldata) external { // min validation
address tokenBorrowed = amount0 > 0 ? token0 : token1; uint amountBorrowed = amount0 > 0 ? amount0 : amount1;
// your logic here: // use borrowed tokens, arbitrage, swap elsewhere, etc.
// same-token repayment minimum uint amountToRepay = UniswapV2Library.getAmountIn( amountBorrowed, reserveIn, reserveOut );
IERC20(tokenBorrowed).transfer(address(pair), amountToRepay);}Single token swap
Section titled “Single token swap”This is the case where the token withdrawn is the same as the token returned.
Let’s calculate the minimum amount we need to return, starting from the invariant:
- If you return more, you are donating
- If you return less, tx reverts
Effective fee
Section titled “Effective fee”It expresses the extra repayment as a percentage of the amount you originally withdrew, not as a percentage of the amount you return.
amountReturned fee:
amountWithdrawn fee:
- fee: 0.3% of the returned/input amount
- effective fee: 0.3009027% of the withdrawn amount
Example
Section titled “Example”function uniswapV2Call( address sender, uint amount0, uint amount1, bytes calldata) external { // min validation
address tokenBorrowed = amount0 > 0 ? token0 : token1; uint amountBorrowed = amount0 > 0 ? amount0 : amount1;
// your logic here: // use borrowed tokens, arbitrage, swap elsewhere, etc.
// same-token repayment minimum uint amountToRepay = (amountBorrowed * 1000 + 996) / 997;
IERC20(tokenBorrowed).transfer(address(pair), amountToRepay);}Comparing fees
Section titled “Comparing fees”Multitoken swap is 0.0009027% cheaper than single token swap.
- 0.3009027% − 0.3000000% = 0.0009027% difference
- 30.09 bps − 30.00 bps ≈ 0.09 bps
Use cases
Section titled “Use cases”Most real uses fall into these buckets:
- Arbitrage — buy on one venue, sell on another, keep spread.
- Liquidation — borrow asset, liquidate unhealthy position elsewhere, receive bonus, repay.
- Collateral swap / debt refinance — use borrowed funds to change a position’s collateral or debt composition in one transaction.
- Self-leveraging / deleveraging — recursively adjust exposure without upfront capital.
- Peg / price dislocation capture — exploit temporary mispricings of wrapped, staked, synthetic, or stable assets.
- Inventory bridging inside one tx — temporarily source inventory needed to finish a larger operation.
- Position migration — move LP, lending, vault, or leveraged positions across protocols.
- Protocol-specific actions — mint, redeem, stake, unwrap, claim, then repay.