Skip to content
Snippets Groups Projects
PoolV3Client.cs 8.59 KiB
using System.Numerics;
using Frontend.Functions;
using Frontend.Events;
using Nethereum.Contracts;
using Nethereum.Hex.HexTypes;
using Nethereum.Web3;
using Nethereum.Web3.Accounts;

using Frontend.Configuration;
using Microsoft.AspNetCore.Components.Forms;
using System.Reflection.Metadata.Ecma335;
using Nethereum.Contracts.Extensions;
using Nethereum.Contracts.Standards.ERC20.TokenList;

namespace Frontend;

public class PoolV3Client
{
    private readonly Contract _pool;
    private readonly Web3 _web3;
    private readonly ChainSettings _chainSettings;
    private readonly Account _account;
    private string? _token0Address = null;
    private string? _token1Address = null;
    private uint? _poolFeeTier = null;
    private TokenClient? _token0Client;
    private TokenClient? _token1Client;


    public string Token0Address
    {
        get
        {
            if (_token0Address == null)
            {
                _token0Address = QueryToken0Address().Result;
            }
            return _token0Address;
        }
    }

    public string Token1Address
    {
        get
        {
            if (_token1Address == null)
            {
                _token1Address = QueryToken1Address().Result;
            }
            return _token1Address;
        }
    }

    private TokenClient Token0Client
    {
        get
        {
            if (_token0Client == null)
            {
                _token0Client = new TokenClient(_web3, Token0Address);
            }
            return _token0Client;
        }
    }
    private TokenClient Token1Client
    {
        get
        {
            if (_token1Client == null)
            {
                _token1Client = new TokenClient(_web3, Token1Address);
            }
            return _token1Client;
        }
    }

    public PoolV3Client(Web3 web3, ChainSettings chainSettings, Account account)
    {
        _account = account;
        _pool = web3.Eth.GetContract(
            File.ReadAllText(
                Path.Combine(
                Directory.GetCurrentDirectory(),
                    "Configuration",
                    "Abi",
                    "uniswapV3Pool.abi.json"
                )
            ),
            chainSettings.Uniswap.RocEthPoolAddress
        );
        _web3 = web3;
        _chainSettings = chainSettings;
    }

    public async Task<decimal> GetPairRatio()
    {
        return (decimal)SqrtPriceX96ToPrice(await GetSqrtPriceX96());
    }

    public async Task<BigInteger> GetSqrtPriceX96()
    {
        var handler = _web3.Eth.GetContractQueryHandler<Slot0Funtion>();
        var func = new Slot0Funtion();
        var slot0Result = await handler.QueryDeserializingToObjectAsync<Slot0OutputDTO>(func, _pool.Address);
        return slot0Result.SqrtPriceX96;
    }

    public double SqrtPriceX96ToPrice(BigInteger sqrtPriceX96)
    {
        var priceX96 = sqrtPriceX96 * sqrtPriceX96;
        return (double)priceX96 / Math.Pow(2, 192);
    }

    public BigInteger PriceToSqrtPriceX96(double price)
    {
        return new BigInteger(Math.Sqrt(price) * Math.Pow(2, 96));
    }

    private async Task<string> QueryToken0Address()
    {
        if (null == _token0Address)
        {
            var handler = _web3.Eth.GetContractQueryHandler<Token0Function>();
            var func = new Token0Function();
            var result = await handler.QueryAsync<string>(_pool.Address, func);
            _token0Address = result;
        }

        return _token0Address;
    }

    private async Task<string> QueryToken1Address()
    {
        if (null == _token1Address)
        {
            var handler = _web3.Eth.GetContractQueryHandler<Token1Function>();
            var func = new Token1Function();
            var result = await handler.QueryAsync<string>(_pool.Address, func);
            _token1Address = result;
        }

        return _token1Address;
    }

    public async Task<uint> QueryPoolFee()
    {
        if (null == _poolFeeTier)
        {
            var handler = _web3.Eth.GetContractQueryHandler<FeeFunction>();
            var func = new FeeFunction();
            var result = await handler.QueryAsync<int>(_pool.Address, func);
            _poolFeeTier = (uint)result;
        }
        return (uint)_poolFeeTier;
    }

    public async Task<BigInteger> QueryLiquidity()
    {
        var handler = _web3.Eth.GetContractQueryHandler<LiquidityFunction>();
        var func = new LiquidityFunction();
        return await handler.QueryAsync<BigInteger>(_pool.Address, func);
    }


    /*
    this function should swap the tokens in the pool
    */
    public async Task<decimal> SwapAsync(string fromTokenAddress, string toTokenAddress, decimal amount, decimal amountOutMinimum = 0, uint sqrtPriceLimitX96 = 0, bool doApproval = true)
    {

        if (doApproval)
        {
            if (fromTokenAddress == Token0Address)
            {
                if (amount > Web3.Convert.FromWei(await Token0Client.GetAllowanceAsync(_account.Address, _chainSettings.Uniswap.SwapRouterV2Address)))
                {
                    await Token0Client.ApproveAsync(_chainSettings.Uniswap.SwapRouterV2Address, BigInteger.Pow(2, 256) - 1);
                }
            }
            else
            {
                if (amount > Web3.Convert.FromWei(await Token0Client.GetAllowanceAsync(_account.Address, _chainSettings.Uniswap.SwapRouterV2Address)))
                {
                    await Token0Client.ApproveAsync(_chainSettings.Uniswap.SwapRouterV2Address, BigInteger.Pow(2, 256) - 1);
                }
            }
        }

        var contractHandler = _web3.Eth.GetContractHandler(_chainSettings.Uniswap.SwapRouterV2Address);
        var exactInputSingleFunction = new ExactInputSingleFunction();
        exactInputSingleFunction.Params = new ExactInputSingleParams
        {
            TokenIn = fromTokenAddress,
            TokenOut = toTokenAddress,
            Fee = await QueryPoolFee(),
            Recipient = _account.Address,
            AmountIn = Web3.Convert.ToWei(amount),
            AmountOutMinimum = Web3.Convert.ToWei(amountOutMinimum), // Set your minimum amount out
            SqrtPriceLimitX96 = sqrtPriceLimitX96,
        };
        var exactInputSingleFunctionTxnReceipt = await contractHandler.SendRequestAndWaitForReceiptAsync(exactInputSingleFunction);
        var swapEvent = exactInputSingleFunctionTxnReceipt.DecodeAllEvents<SwapEventDTO>();
        if (swapEvent.Count > 0)
        {
            var receivedAmount = swapEvent[0].Event.AmountOut;
            return Web3.Convert.FromWei(receivedAmount);
        }
        throw new Exception("Swap event not found in transaction receipt");
    }
    /// <summary>
    /// This function should swap the tokens in the pool to create a desired quote
    /// </summary>
    /// <param name="desiredQuote">the ration between token1/token0</param>
    /// <returns>Quote after the swap</returns>
    public async Task<decimal> PerformSwapTowardsDesiredQuote(decimal desiredQuote)
    {
        var sqrtPriceX96 = await GetSqrtPriceX96();
        var desiredSqrtPriceX96 = PriceToSqrtPriceX96((double)desiredQuote);
        string[] tokenAddresses = [Token0Address, Token1Address];
        var fromTokenIndex = 0;
        var amount = 0.0;
        var liquidity = await QueryLiquidity();
        var balance = new BigInteger(0);
        if (desiredSqrtPriceX96 > sqrtPriceX96)
        { // we have to by token0 to increase demand and drive the price up
            fromTokenIndex = 1; // we want to swap from token1 to token0

            // Taken from getNextSqrtPriceFromAmount1RoundingDown see https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/SqrtPriceMath.sol
            amount = (double)(liquidity * (desiredSqrtPriceX96 - sqrtPriceX96)) / (double)BigInteger.Pow(2, 96);
            balance = await Token1Client.BalanceOf(_account.Address);
        }
        else
        { // whe have to sell token0 to lower the price 

            // Taken from getNextSqrtPriceFromAmount0ReoundingUp see https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/SqrtPriceMath.sol
            amount = (double)(liquidity * BigInteger.Pow(2, 96) * (sqrtPriceX96 - desiredSqrtPriceX96)) / (double)(desiredSqrtPriceX96 * sqrtPriceX96);
            balance = await Token0Client.BalanceOf(_account.Address);
        }
        amount *= 1.0 + (double)await QueryPoolFee() / 1000.0; // add the pool fees
        var bigIntAmount = new BigInteger(amount);

        if (balance < bigIntAmount)
        {
            bigIntAmount = balance;
        }


        var receipt = await SwapAsync(tokenAddresses[fromTokenIndex], tokenAddresses[(fromTokenIndex + 1) % 2], Web3.Convert.FromWei(bigIntAmount));

        return await GetPairRatio();
    }

}