From 4704a025061783f9a0d99f72336ba1a02db983b2 Mon Sep 17 00:00:00 2001
From: David Hintermann <David.Hintermann@ost.ch>
Date: Thu, 21 Nov 2024 15:23:15 +0000
Subject: [PATCH] feat(pool): add swap functionality

---
 Frontend/Configuration/ChainSettings.cs       |  1 +
 Frontend/Events/SwapEventDTO.cs               | 15 ++++
 Frontend/Functions/FeeFunction.cs             |  9 ++
 .../Functions/SwapExactInputSingleFunction.cs | 33 ++++++++
 Frontend/PoolV3Client.cs                      | 83 ++++++++++++++-----
 Frontend/appsettings.json                     |  3 +-
 6 files changed, 124 insertions(+), 20 deletions(-)
 create mode 100644 Frontend/Events/SwapEventDTO.cs
 create mode 100644 Frontend/Functions/FeeFunction.cs
 create mode 100644 Frontend/Functions/SwapExactInputSingleFunction.cs

diff --git a/Frontend/Configuration/ChainSettings.cs b/Frontend/Configuration/ChainSettings.cs
index 000afe6..dc07625 100644
--- a/Frontend/Configuration/ChainSettings.cs
+++ b/Frontend/Configuration/ChainSettings.cs
@@ -4,4 +4,5 @@ public class ChainSettings
         public  int ChainId { get; set; }
         public  string TokenAddress { get; set; }
         public  UniswapSettings Uniswap { get; set; }
+        public string SwapRouterV2Address { get; set; }
     }
\ No newline at end of file
diff --git a/Frontend/Events/SwapEventDTO.cs b/Frontend/Events/SwapEventDTO.cs
new file mode 100644
index 0000000..2acc86b
--- /dev/null
+++ b/Frontend/Events/SwapEventDTO.cs
@@ -0,0 +1,15 @@
+using Nethereum.ABI.FunctionEncoding.Attributes;
+using Nethereum.Contracts;
+using System.Numerics;
+
+namespace Frontend.Events;
+
+[Event("Swap")]
+public class SwapEventDTO : IEventDTO
+{
+    [Parameter("uint256", "amountIn", 1, true)]
+    public BigInteger AmountIn { get; set; }
+
+    [Parameter("uint256", "amountOut", 2, true)]
+    public BigInteger AmountOut { get; set; }
+}
\ No newline at end of file
diff --git a/Frontend/Functions/FeeFunction.cs b/Frontend/Functions/FeeFunction.cs
new file mode 100644
index 0000000..1f2add2
--- /dev/null
+++ b/Frontend/Functions/FeeFunction.cs
@@ -0,0 +1,9 @@
+using Nethereum.ABI.FunctionEncoding.Attributes;
+using Nethereum.Contracts;
+
+namespace Frontend.Functions;
+
+[Function("fee", "uint24")]
+public class FeeFunction : FunctionMessage{
+
+}
\ No newline at end of file
diff --git a/Frontend/Functions/SwapExactInputSingleFunction.cs b/Frontend/Functions/SwapExactInputSingleFunction.cs
new file mode 100644
index 0000000..c729446
--- /dev/null
+++ b/Frontend/Functions/SwapExactInputSingleFunction.cs
@@ -0,0 +1,33 @@
+using System.Numerics;
+using Nethereum.ABI.FunctionEncoding.Attributes;
+using Nethereum.Contracts;
+using HexBigInteger = Nethereum.Hex.HexTypes.HexBigInteger;
+
+namespace Frontend.Functions;
+
+public class SwapExactInputSingleFunction : FunctionMessage
+{
+    [Parameter("address", "tokenIn", 1)]
+    public string TokenIn { get; set; }
+
+    [Parameter("address", "tokenOut", 2)]
+    public string TokenOut { get; set; }
+
+    [Parameter("uint256", "amountIn", 3)]
+    public BigInteger AmountIn { get; set; }
+
+    [Parameter("uint256", "amountOutMinimum", 4)]
+    public BigInteger AmountOutMinimum { get; set; }
+
+    [Parameter("address", "recipient", 5)]
+    public string Recipient { get; set; }
+
+    [Parameter("uint256", "deadline", 6)]
+    public HexBigInteger Deadline { get; set; }
+
+    [Parameter("uint24", "fee", 7)]
+    public uint Fee { get; set; }
+
+    [Parameter("uint160", "sqrtPriceLimitX96", 8)]
+    public BigInteger SqrtPriceLimitX96 { get; set; }
+}
\ No newline at end of file
diff --git a/Frontend/PoolV3Client.cs b/Frontend/PoolV3Client.cs
index 0e82995..4a70635 100644
--- a/Frontend/PoolV3Client.cs
+++ b/Frontend/PoolV3Client.cs
@@ -1,24 +1,25 @@
 using System.Numerics;
 using Frontend.Functions;
+using Frontend.Events;
 using Nethereum.Contracts;
-using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
+using Nethereum.Hex.HexTypes;
 using Nethereum.Web3;
 using Nethereum.Web3.Accounts;
 
-using Microsoft.Extensions.FileProviders.Physical;
-using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+using Frontend.Configuration;
 namespace Frontend;
 
 public class PoolV3Client
 {
     private readonly Contract _pool;
     private readonly Web3 _web3;
+    private readonly ChainSettings _chainSettings;
 
-    private string? token0Address=null;
-    private string? token1Address=null;
+    private string? token0Address = null;
+    private string? token1Address = null;
+    private uint? poolFeeTier = null;
 
-
-    public PoolV3Client(string poolAddress, Web3 web3)
+    public PoolV3Client(string poolAddress, Web3 web3, ChainSettings chainSettings)
     {
 
         _pool = web3.Eth.GetContract(
@@ -33,6 +34,7 @@ public class PoolV3Client
             poolAddress
         );
         _web3 = web3;
+        _chainSettings = chainSettings;
     }
 
     public async Task<decimal> GetPairRatio()
@@ -54,29 +56,72 @@ public class PoolV3Client
     {
         return new BigInteger(Math.Sqrt(price) * Math.Pow(2, 96));
     }
-    public async Task<string> Token0Address(){
-        if(null == token0Address){
+    public async Task<string> 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;
+            var result = await handler.QueryAsync<string>(_pool.Address, func);
+            token0Address = result;
         }
-        
+
         return token0Address;
     }
 
-     public async Task<string> Token1Address(){
-        if(null == token1Address){
+    public async Task<string> 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;
+            var result = await handler.QueryAsync<string>(_pool.Address, func);
+            token1Address = result;
         }
-        
+
         return token1Address;
     }
 
-    public async Task swap(){
-        
+    public async Task<uint> getPoolFeeTier()
+    {
+        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;
+    }
+
+
+    /*
+    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)
+    {
+        var swapFunction = new SwapExactInputSingleFunction
+        {
+            TokenIn = fromTokenAddress,
+            TokenOut = toTokenAddress,
+            AmountIn = Web3.Convert.ToWei(amount),
+            AmountOutMinimum = Web3.Convert.ToWei(amountOutMinimum), // Set your minimum amount out
+            Recipient = account.Address,
+            Deadline = new HexBigInteger(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 90), // 1:30 minutes from now
+            Fee = await getPoolFeeTier(),
+            SqrtPriceLimitX96 = sqrtPriceLimitX96
+        };
+
+        var handler = _web3.Eth.GetContractTransactionHandler<SwapExactInputSingleFunction>();
+        var transactionReceipt = await handler.SendRequestAndWaitForReceiptAsync(_chainSettings.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");
     }
 }
\ No newline at end of file
diff --git a/Frontend/appsettings.json b/Frontend/appsettings.json
index 4464df1..9cbb59b 100644
--- a/Frontend/appsettings.json
+++ b/Frontend/appsettings.json
@@ -12,7 +12,8 @@
       "UniswapV3Factory":"0x0227628f3F023bb0B980b67D528571c95c6DaC1c",
       "QuoterV2":"0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3",
       "WethTokenAddress": "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
-      "RocEthPoolAddress": "0xF22CA6B27eaC677f132945C50Cd3499A9b8a6CBC"
+      "RocEthPoolAddress": "0xF22CA6B27eaC677f132945C50Cd3499A9b8a6CBC",
+      "SwapRouterV2Address": "0x3bFA4769FB09eefC5a80d6E87c3B9C650f7Ae48E"
     }
   },
   "AllowedHosts": "*",
-- 
GitLab