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