Multihop Trades

Introduction

The examples below demonstrate the two styles of multi-hop swapping available in v3. They are not production-ready code and are implemented in a simplified manner for learning purposes.

Setting up the contract

Declare the solidity version that will be used to compile the contract, as well as the helper contracts from Uniswap.

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

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

Create a contract named SwapExamples and declare an immutable public variable, swapRouter, of type ISwapRouter. This variable enables us to call functions from the ISwapRouter interface.

contract ExampleTrades {
    ISwapRouter public immutable brokerbotRouter;

Hardcode the token contract addresses and pool fee tiers for this example. In a production scenario, you would typically use an input parameter and pass it into a memory variable, allowing the contract to dynamically change the brokerbots and tokens it interacts with for each transaction. However, for the sake of conceptual simplicity, we are hardcoding them here.

    address public constant BASE_TOKEN = 0xB4272071eCAdd69d933AdcD19cA99fe80664fc08; //XCHF
    address public constant SHARE_TOKEN = 0x6f38e0f1a73c96cB3f42598613EA3474F09cB200; // DAKS
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    // For this example, we will set the pool fee to 0.3%.
    uint24 public constant poolFee = 3000;
    
    constructor(ISwapRouter _brokerbotRouter) {
        brokerbotRouter = _brokerbotRouter;
    }

Buy Shares via Multi Hop

Buying shares through a multi-hop trade involves swapping a variable amount of the input token for a fixed amount of the share token. This process may include an arbitrary number of intermediary swaps. It's important to note that the Path is encoded in reverse because, when purchasing an exact amount of shares, the trade is executed in reverse order. This reverse execution is necessary to pass the required variables for the chain of transactions.

Input Parameter

  • path: The path is a sequence of (tokenAddress, fee, tokenAddress) encoded in reverse order. These elements are the variables necessary to compute each pool contract address in our sequence of swaps. The BrokerbotRouter respectively the UniswapRouter code will automatically identify the correct pool using these variables and execute the necessary swaps within each pool in our sequence.

  • recipient: The destination address of the outbound asset.

  • deadline: The Unix time specifies when a transaction will be reverted. This is done to protect against extended delays and the increased likelihood of significant price fluctuations during that time.

  • amountOut: The desired amount of shares to buy.

  • amountInMaximum: The maximum amount of USDC willing to be swapped for the specified amountOut of shares.

Calling the Function

    /// @notice buySharesMultihop trades a minimum possible amount of token in  for a fixed amount of shares.
    /// @dev The calling address must approve this contract to spend its token in for this function to succeed. As the amount of input XCHF is variable,
    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.
    /// @param amountOut The exact amount of shares to receive on this trade
    /// @param amountInMaximum The amount of tokeni in we are willing to spend to receive the specified amount of shares.
    /// @return amountIn The amount of token in actually spent in the trade.
    function buySharesMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
        // Transfer the specified amount of base currency tokens (XCHF) to this contract.
        TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amountInMaximum);

        // Approve the router to spend the specified `amountInMaximum` of token in (USDC in this example)
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
        //USDC.approve(address(brokerbotRouter), amountInMaximum);
        TransferHelper.safeApprove(USDC, address(brokerbotRouter), amountInMaximum);
        ISwapRouter.ExactOutputParams memory params =
            ISwapRouter.ExactOutputParams({
                path: abi.encodePacked(SHARE_TOKEN, uint24(0), BASE_TOKEN, poolFee, WETH9, poolFee, USDC),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum
            });
        // Executes trade returning the amountIn needed to spend to receive the desired amountOut.
        amountIn = brokerbotRouter.exactOutput(params);
        // For exact output trades, 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(USDC, address(brokerbotRouter), 0);
            TransferHelper.safeTransfer(USDC, msg.sender, amountInMaximum - amountIn);
        }
    }

Selling Shares via Multi Hop

Selling shares through a multi-hop trade involves swapping a fixed amount of shares for the maximum possible amount of desired output tokens. This process may include an arbitrary number of intermediary swaps in between.

Input Parameters

  • path: The path is a sequence of (tokenAddress, fee, tokenAddress) elements, which are the variables required to calculate each pool contract address in our sequence of swaps. The BrokerbotRouter/UniswapRouter code will automatically identify the correct pool using these variables and execute the necessary swaps within each pool in our sequence.

  • recipient: The destination address of the outbound asset.

  • deadline: The Unix time specifies when a transaction will be reverted. This is done to protect against extended delays and the increased likelihood of significant price fluctuations during that time.

  • amountIn: The amount of shares to sell

  • amountOutMin: The minimum required amount of the outbound asset is the threshold below which the transaction will revert. For the purpose of this example, we are setting it to 0. In a production environment, it's advisable to utilize the BrokerbotQuoter to obtain a quote for the expected price.

Calling the Function

    /// @notice sellSharesMultihop trades an amount of shares for USDC.
    /// @dev The calling address must approve this contract to spend the shares token for this function to succeed. 
    /// @param amountIn The exact amount of shares to receive on this trade
    /// @return amountOut The amount of token in actually spent in the trade.
    function sellSharesMultihop(uint256 amountIn) external returns (uint256 amountOut) {
        // Transfer the specified amount of base currency tokens (XCHF) to this contract.
        TransferHelper.safeTransferFrom(SHARE_TOKEN, msg.sender, address(this), amountIn);

        // Approve the router to spend the specified `amountInMaximum` of token in (USDC in this example)
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
        //USDC.approve(address(brokerbotRouter), amountInMaximum);
        TransferHelper.safeApprove(SHARE_TOKEN, address(brokerbotRouter), amountIn);
        ISwapRouter.ExactInputParams memory params =
            ISwapRouter.ExactInputParams({
                path: abi.encodePacked(SHARE_TOKEN, uint24(0), BASE_TOKEN, poolFee, WETH9, poolFee, USDC),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0
            });
        // Executes trade returning the amountIn needed to spend to receive the desired amountOut.
        amountOut = brokerbotRouter.exactInput(params);
    }

The Full Contract

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

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

contract ExampleTrades {
    ISwapRouter public immutable brokerbotRouter;
    address public constant BASE_TOKEN = 0xB4272071eCAdd69d933AdcD19cA99fe80664fc08; //XCHF
    address public constant SHARE_TOKEN = 0x6f38e0f1a73c96cB3f42598613EA3474F09cB200; // DAKS
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    // For this example, we will set the pool fee to 0.3%.
    uint24 public constant poolFee = 3000;
    
    constructor(ISwapRouter _brokerbotRouter) {
        brokerbotRouter = _brokerbotRouter;
    }
    
    /// @notice buySharesMultihop trades a minimum possible amount of token in  for a fixed amount of shares.
    /// @dev The calling address must approve this contract to spend its token in for this function to succeed. As the amount of input XCHF is variable,
    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.
    /// @param amountOut The exact amount of shares to receive on this trade
    /// @param amountInMaximum The amount of tokeni in we are willing to spend to receive the specified amount of shares.
    /// @return amountIn The amount of token in actually spent in the trade.
    function buySharesMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
        // Transfer the specified amount of base currency tokens (XCHF) to this contract.
        TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amountInMaximum);

        // Approve the router to spend the specified `amountInMaximum` of token in (USDC in this example)
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
        //USDC.approve(address(brokerbotRouter), amountInMaximum);
        TransferHelper.safeApprove(USDC, address(brokerbotRouter), amountInMaximum);
        ISwapRouter.ExactOutputParams memory params =
            ISwapRouter.ExactOutputParams({
                path: abi.encodePacked(SHARE_TOKEN, uint24(0), BASE_TOKEN, poolFee, WETH9, poolFee, USDC),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum
            });
        // Executes trade returning the amountIn needed to spend to receive the desired amountOut.
        amountIn = brokerbotRouter.exactOutput(params);
        // For exact output trades, 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(USDC, address(brokerbotRouter), 0);
            TransferHelper.safeTransfer(USDC, msg.sender, amountInMaximum - amountIn);
        }
    }

    /// @notice sellSharesMultihop trades an amount of shares for USDC.
    /// @dev The calling address must approve this contract to spend the shares token for this function to succeed. 
    /// @param amountIn The exact amount of shares to receive on this trade
    /// @return amountOut The amount of token in actually spent in the trade.
    function sellSharesMultihop(uint256 amountIn) external returns (uint256 amountOut) {
        // Transfer the specified amount of base currency tokens (XCHF) to this contract.
        TransferHelper.safeTransferFrom(SHARE_TOKEN, msg.sender, address(this), amountIn);

        // Approve the router to spend the specified `amountInMaximum` of token in (USDC in this example)
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
        //USDC.approve(address(brokerbotRouter), amountInMaximum);
        TransferHelper.safeApprove(SHARE_TOKEN, address(brokerbotRouter), amountIn);
        ISwapRouter.ExactInputParams memory params =
            ISwapRouter.ExactInputParams({
                path: abi.encodePacked(SHARE_TOKEN, uint24(0), BASE_TOKEN, poolFee, WETH9, poolFee, USDC),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0
            });
        // Executes trade returning the amountIn needed to spend to receive the desired amountOut.
        amountOut = brokerbotRouter.exactInput(params);
    }

Last updated