Skip to main content
Version: V3

Single Swaps

Setting up the Contract#

First we declare the solidity version that will be used to compile the contract, and the abicoder v2 to allow arbitrary nested arrays and structs to be encoded and decoded in calldata, a feature we use when executing a swap.

// SPDX-License-Identifier: GPL-2.0-or-laterpragma solidity =0.7.6;pragma abicoder v2;

Next, import the two contracts we'll be using from the npm package installation

import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';

Here we create our contract called SwapExamples, and declare an immutable public variable swapRouter of type ISwapRouter. This allows us to call functions in the ISwapRouter interface.

contract SwapExamples {    // For the scope of these swap examples,    // we will detail the design considerations when using `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.    // It should be noted that for the sake of these examples, we pass in the swap router as a constructor argument, instead of inheriting it.    // More advanced example contracts will detail how to inherit the swap router safely.    // This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.
    ISwapRouter public immutable swapRouter;

Here we hardcode the token contract addresses and pool fee tiers for our example. In production, you would likely use in input parameter for this and pass the input into a memory variable, allowing you to change what the pools and tokens you are interacting with on a per transaction basis, but for conceptual simplicity we are hardcoding them here.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    // For this example, we will set the pool fee to 0.3%.    uint24 public constant poolFee = 3000;
    constructor(ISwapRouter _swapRouter) {        swapRouter = _swapRouter;    }

Exact Input Swaps#

In order to execute a swap, the caller must approve the contract to withdraw the tokens from the calling address's account. Remember that because our contract is a contract itself, and not an extension of the caller (us), we must also approve the Uniswap protocol router contract to use the tokens that will be in the possession of our contract after they are withdrawn from the calling address.

After that we will transfer the amount of Dai from the calling address into our contract, and use amount as the value passed to the second approve.

    /// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9    /// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.    /// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.    /// @param amountIn The exact amount of DAI that will be swapped for WETH9.    /// @return amountOut The amount of WETH9 received.    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {        // msg.sender must approve this contract
        // Transfer the specified amount of DAI to this contract.        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);
        // Approve the router to spend DAI.        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

Swap Input Parameters#

Now we can execute the swap function. To do this we need to populate the ExactInputSingleParams with the data necessary to execute the swap. These parameters are found in the interfaces which can be browsed here.

A brief overview of the less obvious parameters:

  • tokenIn The contract address of the inbound token
  • tokenOut The contract address of the outbound token
  • fee The fee tier of the pool, used to determine the correct pool contract in which to execute the swap
  • recipient the destination address of the outbound token
  • deadline: the unix time after which a swap will fail, to protect against long pending transactions and wild swings in prices
  • amountOutMinimum: we are setting to zero, but this is a significant risk in production. For a real deployment, this value should be calculated using our SDK or an onchain price oracle - this helps protect against getting an unusually bad price for a trade due to a front running sandwich or another type of price manipulation
  • sqrtPriceLimitX96: We set this to zero - which makes this paramater inactive. In production, this value can be used to set the pricing limit which the swap will push to pool to, which is helpful for protecting against price impact or setting up logic for a variety of price relevant mechanisms.

Calling the function#

        // Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.        // We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.        ISwapRouter.ExactInputSingleParams memory params =            ISwapRouter.ExactInputSingleParams({                tokenIn: DAI,                tokenOut: WETH9,                fee: poolFee,                recipient: msg.sender,                deadline: block.timestamp,                amountIn: amountIn,                amountOutMinimum: 0,                sqrtPriceLimitX96: 0            });
        // The call to `exactInputSingle` executes the swap.        amountOut = swapRouter.exactInputSingle(params);    }

Exact Output swaps#

Exact Output swaps a minimum possible amount of the input token for a fixed amount of the outbound token. This is the less common swap style - but useful in a variety of circumstances.

Because this example transfers in the inbound asset in anticipation of the swap - its possible that some of the inbound token will be left over after the swap is executed, which is why we pay it back to the calling address at the end of the swap.

Calling The Function#

    /// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WETH.    /// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.    /// @param amountOut The exact amount of WETH9 to receive from the swap.    /// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.    /// @return amountIn The amount of DAI actually spent in the swap.    function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {        // Transfer the specified amount of DAI to this contract.        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
        // Approve the router to spend the specified `amountInMaximum` of DAI.        // In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);
        ISwapRouter.ExactOutputSingleParams memory params =            ISwapRouter.ExactOutputSingleParams({                tokenIn: DAI,                tokenOut: WETH9,                fee: poolFee,                recipient: msg.sender,                deadline: block.timestamp,                amountOut: amountOut,                amountInMaximum: amountInMaximum,                sqrtPriceLimitX96: 0            });
        // Executes the swap returning the amountIn needed to spend to receive the desired amountOut.        amountIn = swapRouter.exactOutputSingle(params);
        // For exact output swaps, the amountInMaximum may not have all been spent.        // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.        if (amountIn < amountInMaximum) {            TransferHelper.safeApprove(DAI, address(swapRouter), 0);            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);        }    }

The Full Example Code#

// SPDX-License-Identifier: GPL-2.0-or-laterpragma solidity =0.7.6;pragma abicoder v2;
import '../libraries/TransferHelper.sol';import '../interfaces/ISwapRouter.sol';
contract SwapExamples {    // For the scope of these swap examples,    // we will detail the design considerations when using    // `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.
    // It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity.    // More advanced example contracts will detail how to inherit the swap router safely.
    ISwapRouter public immutable swapRouter;
    // This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.
    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    // For this example, we will set the pool fee to 0.3%.    uint24 public constant poolFee = 3000;
    constructor(ISwapRouter _swapRouter) {        swapRouter = _swapRouter;    }
    /// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9    /// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.    /// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.    /// @param amountIn The exact amount of DAI that will be swapped for WETH9.    /// @return amountOut The amount of WETH9 received.    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {        // msg.sender must approve this contract
        // Transfer the specified amount of DAI to this contract.        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);
        // Approve the router to spend DAI.        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
        // Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.        // We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.        ISwapRouter.ExactInputSingleParams memory params =            ISwapRouter.ExactInputSingleParams({                tokenIn: DAI,                tokenOut: WETH9,                fee: poolFee,                recipient: msg.sender,                deadline: block.timestamp,                amountIn: amountIn,                amountOutMinimum: 0,                sqrtPriceLimitX96: 0            });
        // The call to `exactInputSingle` executes the swap.        amountOut = swapRouter.exactInputSingle(params);    }
    /// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WETH.    /// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.    /// @param amountOut The exact amount of WETH9 to receive from the swap.    /// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.    /// @return amountIn The amount of DAI actually spent in the swap.    function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {        // Transfer the specified amount of DAI to this contract.        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
        // Approve the router to spend the specifed `amountInMaximum` of DAI.        // In production, you should choose the maximum amount to spend based on oracles or other data sources to acheive a better swap.        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);
        ISwapRouter.ExactOutputSingleParams memory params =            ISwapRouter.ExactOutputSingleParams({                tokenIn: DAI,                tokenOut: WETH9,                fee: poolFee,                recipient: msg.sender,                deadline: block.timestamp,                amountOut: amountOut,                amountInMaximum: amountInMaximum,                sqrtPriceLimitX96: 0            });
        // Executes the swap returning the amountIn needed to spend to receive the desired amountOut.        amountIn = swapRouter.exactOutputSingle(params);
        // For exact output swaps, the amountInMaximum may not have all been spent.        // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.        if (amountIn < amountInMaximum) {            TransferHelper.safeApprove(DAI, address(swapRouter), 0);            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);        }    }}