Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ArpitxGit/1b8144568c259dd149138a4cd6894d71 to your computer and use it in GitHub Desktop.
Save ArpitxGit/1b8144568c259dd149138a4cd6894d71 to your computer and use it in GitHub Desktop.

The goal is to

  • Continuously monitor the impermanent loss incurred for a position in Uniswap V3
  • And if there is a loss, we need to exit the position
  • It has to be 100% automated (the end user should not be expected to be online signing the transaction when the liquidity is pulled out)

Monitoring a position

We can use the Uniswap V3's subgraph to know a position's details like tokens, price at the time of providing liquidity, etc. We will also know the current tick in the pool.

We can use the following Impermanent Loss Calculation.

When the loss crosses a threshold (like say 50%), we need to trigger the recovery process.

Exiting a position

We need to deploy a simple smart contract that wraps around Uniswap V3's LP exiting function and trigger this function when the above monitoring logic is executed. The output of this should result in the wallet having the tokens back with the position fuly exited.

Bonus

After exiting the liquidity, it will be great if all the tokens are converted back to USDC at the current market price and deposited into the user's wallet

@ArpitxGit
Copy link
Author

To accomplish this, we can create a simple smart contract that wraps around the Uniswap V3 LP exiting function. The contract will use the Uniswap V3 subgraph to gather information about the user's position, including the tokens and price at the time of providing liquidity, as well as the current tick in the pool.

The contract will use the Impermanent Loss Calculation formula provided in the link to continuously monitor the impermanent loss incurred for the user's position. If the loss crosses a predetermined threshold (such as 50%), the contract will trigger the LP exiting function to exit the position and recover the user's funds.

Once the position is exited, the contract can then convert the tokens back to USDC at the current market price and deposit them into the user's wallet. This entire process is automated and does not require the end user to be online to sign any transactions.

simple smart contract that wraps around Uniswap V3's LP exiting function and triggers it when the impermanent loss for a position reaches a certain threshold:

pragma solidity ^0.8.0;  

import { UniswapV3 } from "https://github.com/Uniswap/uniswap-v3-periphery/contracts/UniswapV3.sol";

contract ImpermanentLossMonitor {
    address public owner;
    UniswapV3 public uniswap;
    mapping(address => bool) public positions;

    constructor() public {
        owner = msg.sender;
        uniswap = UniswapV3.deployed();
    }

    function addPosition(address _lp) public {
        require(msg.sender == owner, "Only the owner can add positions.");
        positions[_lp] = true;
    }

    function removePosition(address _lp) public {
        require(msg.sender == owner, "Only the owner can remove positions.");
        positions[_lp] = false;
    }

    function monitor() public {
        for (address _lp in positions) {
            // Use UniswapV3 subgraph to gather information about the LP position
            // Calculate impermanent loss using the provided formula
            if (impermanentLoss > 50) {
                uniswap.exit(_lp);
            }
        }
    }
}

This contract can be set up to run the monitor() function on a regular interval, such as every hour, to continuously check for any positions that have reached the impermanent loss threshold. Once the position is exited, the user can use other smart contracts or DEXs like Uniswap to convert their tokens back to USDC at the current market price and deposit them into their wallet.

Here's how the monitor function can be completed using the provided impermanent loss calculation formula:

function monitor() public {  
  for (address _lp in positions) {  
        // Use UniswapV3 subgraph to gather information about the LP position  
        variables = {"position_id": _lp};  
        response = client.execute(gql(position_query), variable_values=variables);  
        position = response['positions'][0];  
        liquidity = int(position["liquidity"]);  
        tick_lower = int(position["tickLower"]["tickIdx"]);  
        tick_upper = int(position["tickUpper"]["tickIdx"]);  
        pool_id = position["pool"]["id"];  
        decimals0 = int(position["token0"]["decimals"]);  
        decimals1 = int(position["token1"]["decimals"]);  

        //get pool info for current price  
        variables = {"pool_id": pool_id};  
        response = client.execute(gql(pool_query), variable_values=variables);  
        pool = response['pools'][0];  
        current_tick = int(pool["tick"]);  
        current_sqrt_price = int(pool["sqrtPrice"]) / (2 ** 96);  
        current_price = tick_to_price(current_tick);  
        adjusted_current_price = current_price / (10 ** (decimals1 - decimals0));  
        sa = tick_to_price(tick_lower / 2);  
        sb = tick_to_price(tick_upper / 2);  

        //Calculate impermanent loss using the provided formula  
        if (tick_upper <= current_tick){  
            // Only token1 locked  
            amount0 = 0;  
            amount1 = liquidity * (sb - sa);  
        }else if (tick_lower < current_tick < tick_upper){  
            // Both tokens present  
            amount0 = liquidity * (tick_to_price(current_tick - tick_lower / 2) - sa);  
            amount1 = liquidity * (sb - tick_to_price(current_tick - tick_lower / 2));  
        }else{  
            // Only token0 locked  
            amount0 = liquidity * (sb - sa);  
            amount1 = 0;  
        }  

        impermanent_loss = (amount1 - amount0 * adjusted_current_price) / amount1;  
        if (impermanent_loss > 50) {  
            uniswap.exit(_lp);  
        }  
    }  
}

This function retrieves the position's data from the UniswapV3 subgraph and uses it to calculate the impermanent loss. If the loss is above the threshold of 50%, the function will trigger the LP exiting function to exit the position.
It's worth mentioning that the function need to be executed with a wallet connected that have the authority to call the exit function on the UniswapV3 contract.

There are a few ways to convert all the tokens back to USDC at the current market price and deposit them into the user's wallet after exiting liquidity, here is one example of how this can be done using Uniswap:

  1. Create a new smart contract that acts as an intermediary between your main contract and Uniswap, this smart contract will have a function that takes the token address, the amount and the user's wallet address as inputs.

  2. The intermediary contract will then interact with Uniswap's exchange contract to exchange the tokens for USDC at the current market price. This can be done by calling the eth_sendTransaction method in web3.js, passing in the address of the Uniswap exchange contract, the token address, the amount, and the user's wallet address as inputs.

  3. Once the exchange is done, the smart contract will transfer the USDC to the user's wallet address by calling the transfer function in the ERC20 USDC contract passing in the user's wallet address and the amount as inputs.

  4. The main contract will call the above function in the intermediary contract passing in the necessary inputs and execute it with a transaction coming from the user's wallet.

It's worth noting that this is just one example, there are other ways to interact with Uniswap and other DEXs to exchange tokens for USDC and transfer it to the user's wallet address. Additionally, you might consider to use a ready-made solution like Chainlink oracles to acquire the exchange rate of the tokens with USDC.

Here's an example of how the intermediary contract might look like:

pragma solidity ^0.8.0;  

import "https://github.com/Uniswap/uniswap-v2-core/contracts/interfaces/IUniswapV2Pair.sol";  
import "https://github.com/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol";  

contract TokenExchanger {  
     address public uniswapFactory;  
     IUniswapV2Pair public uniswapPair;  
     address public usdc;  
    
constructor() public {
        uniswapFactory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
        usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // address of USDC contract
    }

      
function exchange(address token, uint256 amount, address to) public payable {
        uniswapPair = IUniswapV2Pair(uniswapFactory.createPair(token, usdc));
        // call the 'swap' function on the Uniswap pair contract
        uniswapPair.swap(amount, uniswapPair.balanceOf(address(this)), uniswapPair.balanceOf(address(this)), address(this), to);
    }  
function depositUsdc(address _to, uint256 _value) public {
        require(address(this).balance >= _value, "Not enough balance to transfer");
        require(_to != address(0), "Invalid address");
        require(_value > 0, "Invalid amount");
        ERC20(_to).transfer(_value);
    }
}

This contract uses Uniswap's factory contract to create a pair with the token and USDC, then call the swap function of the pair, passing the amount of token to exchange and the user's address as the recipient of the USDC.
After that the contract transfer the USDC to the user's wallet address by calling the transfer function of the ERC20 USDC contract, passing the user's address and the amount as inputs.
It's worth mentioning that this is just a basic example, there are different ways to interact with Uniswap and other DEXs, and you might want to add more functionality like input/output validations, error handling and also use a ready-made solution like Chainlink oracles to acquire the exchange rate of the tokens with USDC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment