-
David Hintermann authoredd711a330
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();
}
}