From cbf690d5d949e15a3e4479045c11953dd9ef6c06 Mon Sep 17 00:00:00 2001
From: David Hintermann <David.Hintermann@ost.ch>
Date: Thu, 28 Nov 2024 15:02:55 +0000
Subject: [PATCH] wip: commit before cleanup

---
 Frontend/Functions/ApproveMaxFunctrion.cs  |  13 --
 Frontend/Functions/ExactInputFunction.cs   |  21 ---
 Frontend/Functions/ExactSingleParamBase.cs |  50 +++----
 Frontend/Metrics/Erc20TokenMetrics.cs      |  16 +-
 Frontend/PoolV3Client.cs                   | 164 ++++++++++++---------
 Frontend/Program.cs                        |  15 +-
 Frontend/TokenClient.cs                    |  67 ++++++---
 7 files changed, 178 insertions(+), 168 deletions(-)
 delete mode 100644 Frontend/Functions/ApproveMaxFunctrion.cs
 delete mode 100644 Frontend/Functions/ExactInputFunction.cs

diff --git a/Frontend/Functions/ApproveMaxFunctrion.cs b/Frontend/Functions/ApproveMaxFunctrion.cs
deleted file mode 100644
index c27a0d6..0000000
--- a/Frontend/Functions/ApproveMaxFunctrion.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-
-using System.Numerics;
-using Nethereum.ABI.FunctionEncoding.Attributes;
-using Nethereum.Contracts;
-
-namespace Frontend.Functions; public partial class ApproveMaxFunction : ApproveMaxFunctionBase { }
-
-[Function("approveMax")]
-public class ApproveMaxFunctionBase : FunctionMessage
-{
-    [Parameter("address", "token", 1)]
-    public virtual string Token { get; set; }
-}
\ No newline at end of file
diff --git a/Frontend/Functions/ExactInputFunction.cs b/Frontend/Functions/ExactInputFunction.cs
deleted file mode 100644
index 4bcff67..0000000
--- a/Frontend/Functions/ExactInputFunction.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Nethereum.ABI.FunctionEncoding.Attributes;
-using Nethereum.Contracts;
-using System.Numerics;
-
-namespace Frontend.Functions;
-
-[Function("exactInput")]
-class ExactInputFunction : FunctionMessage
-{
-    [Parameter("uint256", "amountIn", 1)]
-    public BigInteger AmountIn { get; set; }
-
-    [Parameter("uint256", "amountOutMinimum", 2)]
-    public BigInteger AmountOutMinimum { get; set; }
-
-    [Parameter("address[]", "path", 3)]
-    public string[] Path { get; set; }
-
-    [Parameter("address", "to", 4)]
-    public string To { get; set; }
-}
\ No newline at end of file
diff --git a/Frontend/Functions/ExactSingleParamBase.cs b/Frontend/Functions/ExactSingleParamBase.cs
index ae54ab1..935dc61 100644
--- a/Frontend/Functions/ExactSingleParamBase.cs
+++ b/Frontend/Functions/ExactSingleParamBase.cs
@@ -7,32 +7,32 @@ using Nethereum.Contracts.CQS;
 using Nethereum.Contracts;
 
 namespace Frontend.Functions;
-    public class ExactInputSingleParamsBase 
-    {
-        [Parameter("address", "tokenIn", 1)]
-        public virtual string TokenIn { get; set; }
-        [Parameter("address", "tokenOut", 2)]
-        public virtual string TokenOut { get; set; }
-        [Parameter("uint24", "fee", 3)]
-        public virtual uint Fee { get; set; }
-        [Parameter("address", "recipient", 4)]
-        public virtual string Recipient { get; set; }
-        [Parameter("uint256", "amountIn", 5)]
-        public virtual BigInteger AmountIn { get; set; }
-        [Parameter("uint256", "amountOutMinimum", 6)]
-        public virtual BigInteger AmountOutMinimum { get; set; }
-        [Parameter("uint160", "sqrtPriceLimitX96", 7)]
-        public virtual BigInteger SqrtPriceLimitX96 { get; set; }
-    }
+public class ExactInputSingleParamsBase
+{
+    [Parameter("address", "tokenIn", 1)]
+    public virtual string TokenIn { get; set; }
+    [Parameter("address", "tokenOut", 2)]
+    public virtual string TokenOut { get; set; }
+    [Parameter("uint24", "fee", 3)]
+    public virtual uint Fee { get; set; }
+    [Parameter("address", "recipient", 4)]
+    public virtual string Recipient { get; set; }
+    [Parameter("uint256", "amountIn", 5)]
+    public virtual BigInteger AmountIn { get; set; }
+    [Parameter("uint256", "amountOutMinimum", 6)]
+    public virtual BigInteger AmountOutMinimum { get; set; }
+    [Parameter("uint160", "sqrtPriceLimitX96", 7)]
+    public virtual BigInteger SqrtPriceLimitX96 { get; set; }
+}
 
-    public partial class ExactInputSingleParams : ExactInputSingleParamsBase { }
+public partial class ExactInputSingleParams : ExactInputSingleParamsBase { }
 
 
-    [Function("exactInputSingle", "uint256")]
-    public class ExactInputSingleFunctionBase : FunctionMessage
-    {
-        [Parameter("tuple", "params", 1)]
-        public virtual ExactInputSingleParams Params { get; set; }
-    }
+[Function("exactInputSingle", "uint256")]
+public class ExactInputSingleFunctionBase : FunctionMessage
+{
+    [Parameter("tuple", "params", 1)]
+    public virtual ExactInputSingleParams Params { get; set; }
+}
 
-    public partial class ExactInputSingleFunction : ExactInputSingleFunctionBase { }
\ No newline at end of file
+public partial class ExactInputSingleFunction : ExactInputSingleFunctionBase { }
\ No newline at end of file
diff --git a/Frontend/Metrics/Erc20TokenMetrics.cs b/Frontend/Metrics/Erc20TokenMetrics.cs
index 39dca28..a6b4ed8 100644
--- a/Frontend/Metrics/Erc20TokenMetrics.cs
+++ b/Frontend/Metrics/Erc20TokenMetrics.cs
@@ -1,29 +1,33 @@
 using System.Diagnostics.Metrics;
+using Frontend.Configuration;
+using Nethereum.Web3;
 
 namespace Frontend.Metrics;
 
 public class Erc20TokenMetrics
 {
     private readonly IMeterFactory _meterFactory;
-    private readonly TokenClient _tokenClient;
+    private readonly TokenClient _rocTokenClient;
     private ObservableGauge<double>? _totalSupplyGauge;
 
-    public Erc20TokenMetrics(IMeterFactory meterFactory, TokenClient tokenClient)
+    public Erc20TokenMetrics(IMeterFactory meterFactory,Web3 web3, ChainSettings chainSettings)
     {
         _meterFactory = meterFactory;
-        _tokenClient = tokenClient;
+        _rocTokenClient = new(web3,chainSettings.TokenAddress);
     }
 
     public void CreateMetrics()
     {
         var meter = _meterFactory.Create("RoestiCoin");
-        _totalSupplyGauge = meter.CreateObservableGauge("RoestiCoin.Supply.Total", () => GetTotalSupply(), unit: "ROC");
+        _totalSupplyGauge = meter.CreateObservableGauge("RoestiCoin.Supply.Total", () => GetTotalSupply(_rocTokenClient), unit: "ROC");
+    
     }
 
-    private double GetTotalSupply()
+    private double GetTotalSupply(TokenClient client)
     {
-        var totalSupply = _tokenClient.TotalSupply();
+        var totalSupply = client.TotalSupply();
         totalSupply.Wait();
         return (double)totalSupply.Result;
     }
+    
 }
\ No newline at end of file
diff --git a/Frontend/PoolV3Client.cs b/Frontend/PoolV3Client.cs
index 2aba84b..0c54b10 100644
--- a/Frontend/PoolV3Client.cs
+++ b/Frontend/PoolV3Client.cs
@@ -10,6 +10,7 @@ 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;
 
@@ -19,9 +20,60 @@ public class PoolV3Client
     private readonly Web3 _web3;
     private readonly ChainSettings _chainSettings;
 
-    private string? token0Address = null;
-    private string? token1Address = null;
-    private uint? poolFeeTier = null;
+    private string? _token0Address = null;
+    private string? _token1Address = null;
+    private uint? _poolFeeTier = null;
+    private bool _hasApproved = false;
+    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)
     {
@@ -60,76 +112,70 @@ public class PoolV3Client
     {
         return new BigInteger(Math.Sqrt(price) * Math.Pow(2, 96));
     }
-    public async Task<string> Token0Address()
+
+    private async Task<string> QueryToken0Address()
     {
-        if (null == token0Address)
+        if (null == _token0Address)
         {
             var handler = _web3.Eth.GetContractQueryHandler<Token0Function>();
             var func = new Token0Function();
             var result = await handler.QueryAsync<string>(_pool.Address, func);
-            token0Address = result;
+            _token0Address = result;
         }
 
-        return token0Address;
+        return _token0Address;
     }
 
-    public async Task<string> Token1Address()
+    private async Task<string> QueryToken1Address()
     {
-        if (null == token1Address)
+        if (null == _token1Address)
         {
             var handler = _web3.Eth.GetContractQueryHandler<Token1Function>();
             var func = new Token1Function();
             var result = await handler.QueryAsync<string>(_pool.Address, func);
-            token1Address = result;
+            _token1Address = result;
         }
 
-        return token1Address;
+        return _token1Address;
     }
 
     public async Task<uint> getPoolFeeTier()
     {
-        if (null == poolFeeTier)
+        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;
+            _poolFeeTier = (uint)result;
         }
-        return (uint)poolFeeTier;
+        return (uint)_poolFeeTier;
     }
 
 
     /*
     this function should swap the tokens in the pool
     */
-    public async Task<decimal> SwapAsync(string fromTokenAddress, string toTokenAddress, decimal amount, Account account, decimal amountOutMinimum = 0, uint sqrtPriceLimitX96 = 0)
+    public async Task<decimal> SwapAsync(string fromTokenAddress, string toTokenAddress, decimal amount, Account account, decimal amountOutMinimum = 0, uint sqrtPriceLimitX96 = 0, bool doApproval = true)
     {
-        /* var swapFunction = new SwapExactInputSingleFunction
-         {
-             TokenIn = fromTokenAddress,
-             TokenOut = toTokenAddress,
-             Fee = new BigInteger(await getPoolFeeTier()),
-             Recipient = account.Address,
-             AmountIn = Web3.Convert.ToWei(amount),
-             AmountOutMinimum = Web3.Convert.ToWei(amountOutMinimum), // Set your minimum amount out
-             SqrtPriceLimitX96 = sqrtPriceLimitX96,
-             Deadline = new BigInteger(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60 ) // 20 minutes from now
-         };
-
-         swapFunction.Gas= new BigInteger(300000);
-
-         var handler = _web3.Eth.GetContractTransactionHandler<SwapExactInputSingleFunction>();
-
-         var transactionReceipt = await handler.SendRequestAndWaitForReceiptAsync(_chainSettings.Uniswap.SwapRouterV2Address, swapFunction);
-         var swapEvent = transactionReceipt.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");*/
+
+        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
@@ -143,32 +189,7 @@ public class PoolV3Client
             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");
-    }
-    public async Task<decimal> SwapAsync2(string fromTokenAddress, string toTokenAddress, decimal amount, Account account, decimal amountOutMinimum = 0, uint sqrtPriceLimitX96 = 0)
-    {
-        var contract = _web3.Eth.GetContract(
-            File.ReadAllText(
-                Path.Combine(
-                Directory.GetCurrentDirectory(),
-                    "Configuration",
-                    "Abi",
-                    "swapRouterV2.abi.json"
-                )
-            ),
-            _chainSettings.Uniswap.SwapRouterV2Address
-        );
-
-        var exactInputSingle = contract.GetFunction("exactInputSingle");
-
-        var reciept = await exactInputSingle.SendTransactionAndWaitForReceiptAsync(account.Address, null, fromTokenAddress, toTokenAddress, await getPoolFeeTier(), account.Address, Web3.Convert.ToWei(amount), Web3.Convert.ToWei(amountOutMinimum), sqrtPriceLimitX96);
-        var swapEvent = reciept.DecodeAllEvents<SwapEventDTO>();
+        var swapEvent = exactInputSingleFunctionTxnReceipt.DecodeAllEvents<SwapEventDTO>();
         if (swapEvent.Count > 0)
         {
             var receivedAmount = swapEvent[0].Event.AmountOut;
@@ -177,11 +198,10 @@ public class PoolV3Client
         throw new Exception("Swap event not found in transaction receipt");
     }
 
-        public async Task approveMax(string tokenAddress){
-        var contractHandler = _web3.Eth.GetContractHandler(_chainSettings.Uniswap.SwapRouterV2Address);
-         var approveMaxFunction = new ApproveMaxFunction();
-            approveMaxFunction.Token = tokenAddress;
-            var approveMaxFunctionTxnReceipt = await contractHandler.SendRequestAndWaitForReceiptAsync(approveMaxFunction);
+
+    public async Task<decimal> PerformSwapToStablePrice(double desiredPrice)
+    {
+        return 0.0M;
     }
 
 }
\ No newline at end of file
diff --git a/Frontend/Program.cs b/Frontend/Program.cs
index e4dc332..c6ea82a 100644
--- a/Frontend/Program.cs
+++ b/Frontend/Program.cs
@@ -38,17 +38,14 @@ Account account = new Account(
 
 builder.Services.AddSingleton(provider => chainSettings);
 
-builder.Services.AddSingleton(provider => new TokenClient(
-    chainApiUrl,
-    chainConfiguration["TokenAddress"]!,
-    tokenAbi,
-    accountPrivateKey,
-    chainId
-));
 
 builder.Services.AddSingleton(provider=>account);
-
-builder.Services.AddSingleton(provider => new Web3(account, chainApiUrl));
+builder.Services.AddTransient<Web3>(sp =>
+{
+    var chainSettings = sp.GetRequiredService<ChainSettings>();
+    var account = sp.GetRequiredService<Account>();
+    return new Web3(account, chainApiUrl);
+});
 
 builder.Services.AddSingleton<PoolMetrics>();
 
diff --git a/Frontend/TokenClient.cs b/Frontend/TokenClient.cs
index 1a79d7b..61ab43e 100644
--- a/Frontend/TokenClient.cs
+++ b/Frontend/TokenClient.cs
@@ -11,59 +11,49 @@ public class TokenClient
 {
     const int Decimals = 18;
 
-    private readonly Account _account;
-    private readonly Web3 _web3Client;
+    private readonly Web3 _web3;
     private readonly string _tokenAddress;
-    private readonly string _tokenAbi;
 
-    private Contract Contract
-    {
-        get => _web3Client.Eth.GetContract(_tokenAbi, _tokenAddress);
-    }
+    public string TokenAddress => _tokenAddress;
 
     public TokenClient(
-        string apiUrl,
-        string tokenAddress,
-        string tokenAbi,
-        string accountPrivateKey,
-        int chainId
+        Web3 web3,
+        string tokenAddress
     )
     {
         _tokenAddress = tokenAddress;
-        _tokenAbi = tokenAbi;
-        _account = new(accountPrivateKey, chainId: chainId);
-        _web3Client = new(_account, apiUrl);
+        _web3 = web3;
     }
 
     public async Task<decimal> TotalSupply()
     {
-        var totalSupplyHandler = _web3Client.Eth.GetContractQueryHandler<TotalSupplyFunction>();
+        var totalSupplyHandler = _web3.Eth.GetContractQueryHandler<TotalSupplyFunction>();
         var totalSupplyFunction = new TotalSupplyFunction();
-        var totalSupply = await totalSupplyHandler.QueryAsync<BigInteger>(Contract.Address, totalSupplyFunction);
+        var totalSupply = await totalSupplyHandler.QueryAsync<BigInteger>(_tokenAddress, totalSupplyFunction);
         return ConvertSmallUnitToBigUnit(totalSupply);
     }
 
     public async Task Mint(string recipientAddress, BigInteger amount, CancellationToken cancellationToken)
     {
-        var mintHandler = _web3Client.Eth.GetContractTransactionHandler<MintFunction>();
+        var mintHandler = _web3.Eth.GetContractTransactionHandler<MintFunction>();
         var mintFunction = new MintFunction()
         {
             To = recipientAddress,
             Amount = amount,
         };
 
-        await mintHandler.SendRequestAndWaitForReceiptAsync(Contract.Address, mintFunction, cancellationToken);
+        await mintHandler.SendRequestAndWaitForReceiptAsync(_tokenAddress, mintFunction, cancellationToken);
     }
 
     public async Task Burn(BigInteger amount, CancellationToken cancellationToken)
     {
-        var burnHandler = _web3Client.Eth.GetContractTransactionHandler<BurnFunction>();
+        var burnHandler = _web3.Eth.GetContractTransactionHandler<BurnFunction>();
         var burnFunction = new BurnFunction()
         {
             Amount = amount,
         };
 
-        await burnHandler.SendRequestAndWaitForReceiptAsync(Contract.Address, burnFunction, cancellationToken);
+        await burnHandler.SendRequestAndWaitForReceiptAsync(_tokenAddress, burnFunction, cancellationToken);
     }
 
     public decimal ConvertSmallUnitToBigUnit(BigInteger smallUnit)
@@ -80,10 +70,43 @@ public class TokenClient
             Spender = spenderAddress
         };
 
-        var handler = _web3Client.Eth.GetContractQueryHandler<AllowanceFunction>();
+        var handler = _web3.Eth.GetContractQueryHandler<AllowanceFunction>();
         var allowance = await handler.QueryAsync<BigInteger>(_tokenAddress, allowanceFunction);
 
         return allowance;
     }
 
+    public async Task<string> ApproveMaxAsync(string spenderAddress)
+    {
+        return await ApproveAsync(spenderAddress, BigInteger.Pow(2, 256) - 1);
+    }
+
+    public async Task<string> ApproveAsync(string spenderAddress, BigInteger amount)
+    {
+        var approveFunction = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction
+        {
+            Spender = spenderAddress,
+            Value = amount
+        };
+
+        var handler = _web3.Eth.GetContractTransactionHandler<Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction>();
+        var transactionReceipt = await handler.SendRequestAndWaitForReceiptAsync(_tokenAddress, approveFunction);
+
+        return transactionReceipt.TransactionHash;
+    }
+
+    public async Task<string> TransferAsync(string to, BigInteger amount)
+    {
+        var transferFunction = new TransferFunction
+        {
+            To = to,
+            Value = amount
+        };
+
+        var handler = _web3.Eth.GetContractTransactionHandler<TransferFunction>();
+        var transactionReceipt = await handler.SendRequestAndWaitForReceiptAsync(_tokenAddress, transferFunction);
+
+        return transactionReceipt.TransactionHash;
+    }
+
 }
-- 
GitLab