diff --git a/Frontend/BackgroundServices/PrometheusService.cs b/Frontend/BackgroundServices/PrometheusService.cs index 0a617016651548d32404b8ced5c72ee365fb2f7d..677f6dd2fac3e8cf5a7d8babc25808a090b8e8f4 100644 --- a/Frontend/BackgroundServices/PrometheusService.cs +++ b/Frontend/BackgroundServices/PrometheusService.cs @@ -9,10 +9,9 @@ namespace Frontend.BackgroundServices; public class PrometheusService : BackgroundService { - private readonly ILogger<PrometheusService> _logger ; + private readonly ILogger<PrometheusService> _logger; private readonly TimeSpan _interval = TimeSpan.FromSeconds(10); private readonly Erc20TokenMetrics _erc20TokenMetrics; - private readonly EthMetrics _ethMetrics; private readonly MeterListener _meterListener; private readonly PoolMetrics _poolMetrics; @@ -21,7 +20,6 @@ public class PrometheusService : BackgroundService public PrometheusService( ILogger<PrometheusService> logger, Erc20TokenMetrics tokenMetrics, - EthMetrics ethMetrics, PoolMetrics poolMetrics, ChainSettings chainSettings, Web3 web3, @@ -32,7 +30,6 @@ public class PrometheusService : BackgroundService { _logger = logger; _erc20TokenMetrics = tokenMetrics; - _ethMetrics = ethMetrics; _poolMetrics = poolMetrics; _interval = TimeSpan.FromSeconds(10); _meterListener = new MeterListener(); @@ -42,10 +39,9 @@ public class PrometheusService : BackgroundService listener.EnableMeasurementEvents(instrument); }; _erc20TokenMetrics.CreateMetrics(); - _ethMetrics.CreateMetrics(); potatoStorageMetrics.CreateMetrics(); potatoMarketplaceMetrics.CreateMetrics(); - PoolV3Client poolClient = new( web3,chainSettings,account); + PoolV3Client poolClient = new(web3, chainSettings, account); _poolMetrics.CreateMetrics(poolClient); } diff --git a/Frontend/Marketplaces/Interfaces/IPotatoMarketplace.cs b/Frontend/Marketplaces/Interfaces/IPotatoMarketplace.cs index 757223d802fe535e5395f22715d2f3830b54dbb3..9be98564cfb6e7d7c882febbdaf0833ff10b3e92 100644 --- a/Frontend/Marketplaces/Interfaces/IPotatoMarketplace.cs +++ b/Frontend/Marketplaces/Interfaces/IPotatoMarketplace.cs @@ -1,13 +1,16 @@ +using System.Numerics; using Frontend.Assets; +using Nethereum.Web3; namespace Frontend.Marketplaces.Interfaces { public interface IPotatoMarketPlace { - public double PotatoPrice { get; } - public double PotatoKgToChf(double kg); - public double ChfToPotatoKg(double chf); - public PotatoTransaction Buy(double amountInChf); - public double Sell(PotatoTransaction transaction); + public Task<double> EthPriceInChf { get; } + public Task<double> PotatoKgPriceInChf { get; } + public Task<double> PotatoKgPriceInEth { get; } + public Task<bool> Approve(Web3 approverClient, BigInteger amount); + public Task<PotatoTransaction?> Buy(string ownerAddress); + public Task<bool> Sell(PotatoTransaction transaction, string receiverAddress); } } diff --git a/Frontend/Marketplaces/PotatoMarketplaceMock.cs b/Frontend/Marketplaces/PotatoMarketplaceMock.cs index fb3accf14f4242772fef27eeaf06fc3da2bab29e..2d2024afb9a66865b58a89aaf50fb44a9b6179bb 100644 --- a/Frontend/Marketplaces/PotatoMarketplaceMock.cs +++ b/Frontend/Marketplaces/PotatoMarketplaceMock.cs @@ -1,32 +1,110 @@ +using System.Numerics; using Frontend.Assets; using Frontend.Marketplaces.Interfaces; +using Nethereum.Contracts.Standards.ERC20.ContractDefinition; +using Nethereum.Web3; +using Nethereum.Web3.Accounts; +using Newtonsoft.Json.Linq; namespace Frontend.Marketplaces; -public class PotatoMarketplaceMock() : IPotatoMarketPlace +public class PotatoMarketplaceMock( + Account marketplaceAccount, + Web3 marketplaceWeb3Client, + string wethAddress +) : IPotatoMarketPlace { - private const double potatoKgPrice = 2.02; // source: https://www.blw.admin.ch/blw/de/home/markt/marktbeobachtung/kartoffeln.html + private const double potatoKgChfPrice = 2.02; // source: https://www.blw.admin.ch/blw/de/home/markt/marktbeobachtung/kartoffeln.html - public double PotatoPrice => potatoKgPrice; + private readonly Account account = marketplaceAccount; + private readonly Web3 web3 = marketplaceWeb3Client; + private readonly string wethAddress = wethAddress; - public double ChfToPotatoKg(double chf) + public Task<double> EthPriceInChf => EthChfPrice(); + public Task<double> PotatoKgPriceInChf => Task.FromResult(potatoKgChfPrice); + public Task<double> PotatoKgPriceInEth => PotatoKgEthPrice(); + + public async Task<bool> Approve(Web3 approverClient, BigInteger amountInWei) { - return chf / potatoKgPrice; + var approveHandler = approverClient.Eth.GetContractTransactionHandler<ApproveFunction>(); + var approveFunction = new ApproveFunction + { + Spender = account.Address, + Value = amountInWei, + }; + + var receipt = await approveHandler.SendRequestAndWaitForReceiptAsync(wethAddress, approveFunction); + return receipt.Status.Value == 1; } - public double PotatoKgToChf(double kg) + public async Task<PotatoTransaction?> Buy(string ownerAddress) { - return kg * potatoKgPrice; + var allowanceHandler = web3.Eth.GetContractQueryHandler<AllowanceFunction>(); + var allowanceFunction = new AllowanceFunction + { + Owner = ownerAddress, + Spender = account.Address + }; + + var allowance = await allowanceHandler.QueryAsync<BigInteger>(wethAddress, allowanceFunction); + + var transferHandler = web3.Eth.GetContractTransactionHandler<TransferFromFunction>(); + var transferFunction = new TransferFromFunction + { + From = ownerAddress, + To = account.Address, + Value = allowance, + }; + + var receipt = await transferHandler.SendRequestAndWaitForReceiptAsync(wethAddress, transferFunction); + if (receipt.Status.Value == 0) + { + return null; + } + + var potatoWeight = (double)allowance / (double)await PotatoKgWeiPrice(); + return new PotatoTransaction + { + Weight = potatoWeight + }; + } + + public async Task<bool> Sell(PotatoTransaction transaction, string receiverAddress) + { + var amount = (double)await PotatoKgWeiPrice() * transaction.Weight; + + var transferHandler = web3.Eth.GetContractTransactionHandler<TransferFunction>(); + var transferFunction = new TransferFunction + { + To = receiverAddress, + Value = new BigInteger(amount), + }; + + var receipt = await transferHandler.SendRequestAndWaitForReceiptAsync(wethAddress, transferFunction); + return receipt.Status.Value == 1; + } + + private static async Task<double> EthChfPrice() + { + string url = "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=CHF"; + + using var client = new HttpClient(); + HttpResponseMessage response = await client.GetAsync(url); + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + JObject json = JObject.Parse(responseBody); + double priceInChf = json["CHF"]!.Value<double>(); + return priceInChf; } - public PotatoTransaction Buy(double amountInChf) + private static async Task<double> PotatoKgEthPrice() { - var amountInKg = ChfToPotatoKg(amountInChf); - return new() { Weight = amountInKg }; + var chfEthPrice = 1 / await EthChfPrice(); + return chfEthPrice * potatoKgChfPrice; } - public double Sell(PotatoTransaction transaction) + private static async Task<BigInteger> PotatoKgWeiPrice() { - return PotatoKgToChf(transaction.Weight); + return Web3.Convert.ToWei(await PotatoKgEthPrice()); } } diff --git a/Frontend/Metrics/EthMetrics.cs b/Frontend/Metrics/EthMetrics.cs deleted file mode 100644 index 6395b06abee2834e03313fb9df77e5d79d95f1c6..0000000000000000000000000000000000000000 --- a/Frontend/Metrics/EthMetrics.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Diagnostics.Metrics; -using System.Net.Http; -using Newtonsoft.Json.Linq; -namespace Frontend.Metrics; - -public class EthMetrics{ - private readonly IMeterFactory _meterFactory; - private ObservableGauge<double>? _totalSupplyGauge; - - public EthMetrics(IMeterFactory meterFactory) - { - _meterFactory = meterFactory; - } - - public void CreateMetrics() - { - var meter = _meterFactory.Create("RoestiCoin"); - _totalSupplyGauge = meter.CreateObservableGauge("Ethereum.Price.Current", () => GetCurrentPriceInChf(), unit: "CHF"); - } - - public double GetCurrentPriceInChf(){ - string url = "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=CHF"; - using (HttpClient client = new HttpClient()) - { - HttpResponseMessage response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - string responseBody = response.Content.ReadAsStringAsync().Result; - JObject json = JObject.Parse(responseBody); - double priceInChf = json["CHF"]!.Value<double>(); - return priceInChf; - } - - } -} \ No newline at end of file diff --git a/Frontend/Metrics/PotatoMarketplaceMetrics.cs b/Frontend/Metrics/PotatoMarketplaceMetrics.cs index 57f839e3f7035cad62b0bee55c972d5b233ef634..afac2e86bc78fa685f646be9d935d5ac0acdecf8 100644 --- a/Frontend/Metrics/PotatoMarketplaceMetrics.cs +++ b/Frontend/Metrics/PotatoMarketplaceMetrics.cs @@ -14,6 +14,8 @@ public class PotatoMarketplaceMetrics( public void CreateMetrics() { var meter = MeterFactory.Create("RoestiCoin"); - meter.CreateObservableGauge("Marketplace.Potato.Price", () => marketplace.PotatoPrice, unit: "CHF"); + meter.CreateObservableGauge("Marketplace.Potato.Price.CHF", () => marketplace.PotatoKgPriceInChf.Result, unit: "CHF"); + meter.CreateObservableGauge("Marketplace.Potato.Price.ETH", () => marketplace.PotatoKgPriceInEth.Result, unit: "ETH"); + meter.CreateObservableGauge("Marketplace.Ethereum.Price.CHF", () => marketplace.EthPriceInChf.Result, unit: "CHF"); } } diff --git a/Frontend/Program.cs b/Frontend/Program.cs index de4846dfcab0ff3d866bf6e498af6dabfbb4a974..142bd0d5574efb7c5a3930c96ab77f97631f6bdd 100644 --- a/Frontend/Program.cs +++ b/Frontend/Program.cs @@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using Org.BouncyCastle.Utilities; +using System.Numerics; // the location differ in dev and releas mode. var tokenAbiLocation = Environment.GetEnvironmentVariable("TOKEN_ABI_LOCATION")!; @@ -20,6 +21,7 @@ var tokenAbi = File.ReadAllText(tokenAbiLocation)!; var chainApiUrl = Environment.GetEnvironmentVariable("API_URL")!; var accountPrivateKey = Environment.GetEnvironmentVariable("ACCOUNT_PRIVATE_KEY")!; +var marketplaceAccountPrivateKey = Environment.GetEnvironmentVariable("MARKETPLACE_ACCOUNT_PRIVATE_KEY")!; var postgresHost = Environment.GetEnvironmentVariable("POSTGRES_HOST")!; var postgresUser = Environment.GetEnvironmentVariable("POSTGRES_USER")!; var postgresPassword = Environment.GetEnvironmentVariable("POSTGRES_PASSWORD")!; @@ -35,20 +37,23 @@ chainConfiguration.Bind(chainSettings); // Add services to the container. builder.Services.AddHostedService<PrometheusService>(); builder.Services.AddSingleton<Erc20TokenMetrics>(); -builder.Services.AddSingleton<EthMetrics>(); builder.Services.AddSingleton<PotatoStorageMetrics>(); builder.Services.AddSingleton<PotatoMarketplaceMetrics>(); -builder.Services.AddSingleton<IPotatoMarketPlace>(sp => new PotatoMarketplaceMock()); Account account = new Account( accountPrivateKey, chainId: chainSettings.ChainId ); +Account marketplaceAccount = new( + marketplaceAccountPrivateKey, + chainId: chainSettings.ChainId +); + builder.Services.AddSingleton(provider => chainSettings); -builder.Services.AddSingleton(provider=>account); +builder.Services.AddSingleton(provider => account); builder.Services.AddTransient<Web3>(sp => { var chainSettings = sp.GetRequiredService<ChainSettings>(); @@ -57,8 +62,12 @@ builder.Services.AddTransient<Web3>(sp => }); builder.Services.AddSingleton<PoolMetrics>(); - - +builder.Services.AddSingleton<IPotatoMarketPlace>(sp => +{ + var chainSettings = sp.GetRequiredService<ChainSettings>(); + var web3Client = new Web3(marketplaceAccount, chainApiUrl); + return new PotatoMarketplaceMock(marketplaceAccount, web3Client, chainSettings.WethTokenAddress); +}); builder.Services.AddDbContextFactory<PotatoStorage>(options => options.UseNpgsql($"Host={postgresHost};Database={postgresDb};Username={postgresUser};Password={postgresPassword}"));