Introduction to Solidity Single Hop Swap And How To Implement It
Tags: solidity
Published: 2022-11-15 09:58:26

Cryptocurrency is tricky. One does not simply go around sending funds from one blockchain to another. Make a single mistake and all your funds can go missing with no chance of recovery!

It is also not possible to send tokens from one blockchain to another. There will be a loss of funds if you try to send Bitcoin to an Ethereum address or vice versa as each of these blockchains operates independently and in its ecosystem without any connection between them. 

However, some projects have attempted at resolving this problem of interoperability such as Polkadot, Cosmos, Chainlink, and others. But all of these target developers rather than end-users. Some workarounds have been invented, though, each one with its own set of obstacles. 

In this post, we are going to discuss Single Hop Swaps and what they do. Let’s get started!
What Is A Single Hop Swap?
In the cryptocurrency world, swapping refers to exchanging one coin or token for another. Pretty simple right? But when you have over 1,500 different types of crypto coins, things can get a little clunky.

In order to finalize the Swap, most likely you will use a (usually, centralized) service. This is similar to trade, with the primary difference being that here, no fiat currency is involved. Instead, the trade is of cryptocurrencies. During a Swap, you pay with an alternative cryptocurrency different than the crypto you will purchase.

In the case of a single hop swap, two token owners agree to exchange their tokens for any amount they agree on. The smart contract program sees that they both agreed to it, so it executes the trade for them. The transaction is recorded in the blockchain and validated by the network nodes, and then a new block is opened for another transaction.

How to Perform a Single Hop Trade?
Uniswap allows you to perform two different swaps, the single-hop swap, and the multi-hop swap. Let’s see how!

The following example shows how to create a single hop swap contract. It makes use of two functions: 

swapExactInputSingle
swapExactOutputSingle
swapExactInputSingle
The swapExactInputSingle function is used to perform exact input swaps, which swaps a fixed amount of one token for a maximum possible amount of another token. This function uses the ExactInputSingleParams struct and the exactInputSingle function from the ISwapRouter interface.
swapExactOutputSingle
The swapExactOutputSingle function is used to perform exact output swaps, which swap a minimum possible amount of one token for a fixed amount of another token. This function uses the ExactOutputSingleParams struct and the exactOutputSingle function from the ISwapRouter interface.

The first step is to set up the contract. Declare the Solidity version which will be used to compile the contract, and abicoder v2 to allow arbitrary nested arrays and structs to be encoded and decoded in calldata, a feature used when executing a swap.

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

Step two is to import the two relevant contracts from the npm package installation.

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

In step 3, create a contract, named SwapExamples and declare the immutable public variable.

contract SwapExamples {
    ISwapRouter public immutable swapRouter;

Next, we have hard coded the contract addressed for simplicity. You can use dynamic storage variables for the addresses as well.

    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;
    }

Now is the time to perform Exact Input Swaps. The caller must approve the contract to withdraw the tokens from the calling address's account to execute a swap. Then, transfer the amount of Dai from the calling address into our contract, and use the amount as the value passed to the second approval.

        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Approve the router to spend DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

Now we will swap the input parameters. To execute the swap function, we need to populate the ExactInputSingleParams with the necessary swap data. A brief overview of parameters is given below:
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 parameter inactive. In production, this value can be used to set the limit for the price the swap will push the pool to, which can help protect against price impact or for setting up logic in a variety of price-relevant mechanisms.

        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);
    }


Now we perform the Exact Output swaps which is 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.


TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);

         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
            })

        amountIn = swapRouter.exactOutputSingle(params);

               if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
        }
    }


Combine all the steps into a single code and you’re done, that is how you can carry out a single hop swap trade! 

What’s new in Uniswap v3?
A new feature that enables liquidity suppliers to specify a legitimate pricing range. When the pool is not currently within the range, their liquidity is not taken into account. This not only makes capital far more efficient, but it also lowers the danger of temporary loss for liquidity providers.
The three different levels are: 

Stable Pairs: 0.05%. These fees are designed to apply to pairs like USDT/DAI that have less risk of price changes. Since both are stable coins, there is extremely little chance that they will ever be lost. Since it would enable extremely affordable swaps between stable coins, this is especially intriguing for traders.

Medium Risk Pairs: 0.30%. The non-related pairs that have a high trading volume/popularity are considered medium risk. The volatile risk in the popular pairs is slightly lower. 

High Risk Pairs: 1.00%. The exotic pairs that incur the highest trading fee of 1% are high risk for liquidity providers. 

How to integrate Single Swap v3? 
The ease with which you may incorporate Uniswap into your own smart contract may be one of the factors contributing to its popularity. Consider a system where users can pay with DAI. It would only take a few lines of code to offer the option for them to pay in ETH with Uniswap. Before the real logic, the ETH can be automatically converted into DAI.

Things To Unpack Related To Single Swap
When understanding Uniswap, it becomes imperative to consider some of the most crucial things related to it. These are:
Swap Router: Also known as the wrapper contract that Uniswap provides, the swap router has a lot of convenience functions and safety mechanisms. It can be easily instantiated by using ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564) for any main or testnet.

WETH: In single swap, every ETH must be converted to WETH as there are no direct pairs of ETH. 

sqrtPriceLimitX96: It can be used to establish limits on the pool prices that the swap cannot reach. It is ignored if you set it to 0.

exactOutputSingle: With the help of this function, ETH can be used to purchase a specific number of tokens. Any ETH that is left over will be repaid, but not immediately!

Fee: A non-stable but popular pair is known as the fee. 

Refund: It is possible to return the leftover ETH after the trade is completed. If your contract potentially have an ETH balance for other reasons, make sure to alter this since doing so sends out all of the contract's ETH.


Author Bio: 

David Henry is a Managing Security Consultant at a reputed **[blockchain security audit firm](https://www.immunebytes.com/)** . He is a Fellow of the IISP and a Security Auditor (Lead CCP) and Security Adviser (Lead CCP) accredited by the NCSC (Senior CCP). He is recognized as the co-inventor of LDAP and has specific technical expertise in Secure Information Exchange and Identity Systems. Due to his background as a certified ISO 27001 auditor, he also has a keen interest in security audits.