From 24853a5aec665410c63a566b74d7c90fd91ec43c Mon Sep 17 00:00:00 2001 From: ilior <78041027+itailiors@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:22:31 +0300 Subject: [PATCH] Btc to fiat rest api (#147) * prototype * add more currencies * move the logic to service * cleanup * switch from coingecko to mempool * fix merge * move to component * overall changes for the CurrencyService.cs * change CurrencyRate to its own service named CurrencyRateService.cs * refactor, not finished * only return conversion if btc * Refactored Bitcoin price fetching and conversion logic - Optimized CurrencyRateService with caching for API calls. - Improved CurrencyService for converting and formatting BTC balances. - Enhanced code structure for better readability and maintainability. * Update Wallet.razor * Update nuget packages * Update Index.razor * Update Wallet.razor * Use GetBtcValuesInPreferredCurrency for fiat conversion. * Update Angor.Client.csproj --------- Co-authored-by: Milad Raeisi --- src/Angor.Test/Angor.Test.csproj | 6 +- src/Angor/Client/Angor.Client.csproj | 5 +- .../Client/Components/BalanceDisplay.razor | 42 +++ src/Angor/Client/Pages/Index.razor | 5 +- src/Angor/Client/Pages/Settings.razor | 48 ++- src/Angor/Client/Pages/Wallet.razor | 284 ++++++++++-------- src/Angor/Client/Program.cs | 10 +- .../Client/Services/CurrencyRateService.cs | 98 ++++++ src/Angor/Client/Services/CurrencyService.cs | 118 ++++++++ .../Client/Services/ICurrencyRateService.cs | 4 + src/Angor/Client/Services/ICurrencyService.cs | 5 + src/Angor/Client/Storage/ClientStorage.cs | 12 + src/Angor/Client/Storage/ICacheStorage.cs | 2 + src/Angor/Client/Storage/IClientStorage.cs | 3 + .../Client/Storage/LocalSessionStorage.cs | 94 +++--- .../Client/wwwroot/assets/icons/currency.svg | 7 + src/Angor/Server/Angor.Server.csproj | 2 +- 17 files changed, 570 insertions(+), 175 deletions(-) create mode 100644 src/Angor/Client/Components/BalanceDisplay.razor create mode 100644 src/Angor/Client/Services/CurrencyRateService.cs create mode 100644 src/Angor/Client/Services/CurrencyService.cs create mode 100644 src/Angor/Client/Services/ICurrencyRateService.cs create mode 100644 src/Angor/Client/Services/ICurrencyService.cs create mode 100644 src/Angor/Client/wwwroot/assets/icons/currency.svg diff --git a/src/Angor.Test/Angor.Test.csproj b/src/Angor.Test/Angor.Test.csproj index 6da49efa..17a933e7 100644 --- a/src/Angor.Test/Angor.Test.csproj +++ b/src/Angor.Test/Angor.Test.csproj @@ -19,11 +19,11 @@ - + - + - + diff --git a/src/Angor/Client/Angor.Client.csproj b/src/Angor/Client/Angor.Client.csproj index 7a06ccd2..08d6413b 100644 --- a/src/Angor/Client/Angor.Client.csproj +++ b/src/Angor/Client/Angor.Client.csproj @@ -11,11 +11,12 @@ - - + + + diff --git a/src/Angor/Client/Components/BalanceDisplay.razor b/src/Angor/Client/Components/BalanceDisplay.razor new file mode 100644 index 00000000..e77b4998 --- /dev/null +++ b/src/Angor/Client/Components/BalanceDisplay.razor @@ -0,0 +1,42 @@ +@using Angor.Shared +@using Blockcore.Networks +@inject INetworkConfiguration NetworkConfiguration + + +
+ + @BtcBalance @_network.CoinTicker + + @if (ShowFiatInline && PreferredCurrency != "BTC" && !string.IsNullOrEmpty(BtcBalanceInFiat)) + { +
@BtcBalanceInFiat
+ } +
+ +@code { + [Parameter] + public decimal BtcBalance { get; set; } + + [Parameter] + public string BtcBalanceInFiat { get; set; } + + [Parameter] + public string PreferredCurrency { get; set; } + + [Parameter] + public bool ShowFiatInline { get; set; } = false; + + private Network _network; + + protected override void OnInitialized() + { + _network = NetworkConfiguration.GetNetwork(); + base.OnInitialized(); + } + + private string GetTooltip() => + PreferredCurrency != "BTC" && !string.IsNullOrEmpty(BtcBalanceInFiat) && !ShowFiatInline + ? $"Equivalent: {BtcBalanceInFiat}" + : string.Empty; +} + diff --git a/src/Angor/Client/Pages/Index.razor b/src/Angor/Client/Pages/Index.razor index 525a5652..8d85748d 100644 --- a/src/Angor/Client/Pages/Index.razor +++ b/src/Angor/Client/Pages/Index.razor @@ -1,5 +1,4 @@ @page "/" - Angor
@@ -20,10 +19,8 @@
Welcome to Angor
-

Stay in control of your investments with Angor

+

Stay in control of your investments with Angor

- - diff --git a/src/Angor/Client/Pages/Settings.razor b/src/Angor/Client/Pages/Settings.razor index 6f4362bc..941ed6b5 100644 --- a/src/Angor/Client/Pages/Settings.razor +++ b/src/Angor/Client/Pages/Settings.razor @@ -305,6 +305,41 @@ +@* Currency Display Settings *@ +
+
+
+
+ + + +
+
+ Currency Display +
+

+ Current: @selectedCurrency +

+
+
+
+ + +
+
+
+
+ + @* Wipe Storage *@
@@ -392,6 +427,8 @@ private bool confirmWipe = false; private bool showConfirmWipeMessage = false; private string selectedNetwork = "testnet"; // Default to "testnet" + + private string selectedCurrency = "BTC"; // Default to BTC private SettingsInfo settingsInfo; @@ -404,10 +441,12 @@ networkType = _networkConfiguration.GetNetwork().Name; _networkService.OnStatusChanged += UpdateUI; + + selectedCurrency = _clientStorage.GetCurrencyDisplaySetting(); if (!networkType.ToLower().Contains("test")) selectedNetwork = "mainnet"; - + return base.OnInitializedAsync(); } @@ -650,4 +689,11 @@ NavMenuState.NotifyStateChanged(); } + + private void OnCurrencyChanged(ChangeEventArgs e) + { + selectedCurrency = e.Value.ToString(); + _clientStorage.SetCurrencyDisplaySetting(selectedCurrency); + StateHasChanged(); + } } \ No newline at end of file diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index 354e7ed4..2f66096f 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -1,13 +1,11 @@ @page "/wallet" -@using Blockcore.NBitcoin -@using Angor.Shared -@using Angor.Client.Services + +@using System.Text.Json @using Angor.Client.Storage +@using Angor.Shared @using Angor.Shared.Models -@using Angor.Shared.Services -@using Angor.Client.Components +@using Blockcore.NBitcoin @using Blockcore.Networks -@using System.Text.Json @inject HttpClient _httpClient; @inject IClientStorage storage; @@ -16,23 +14,22 @@ @inject ILogger Logger; @inject IWalletOperations _walletOperations @inject IClipboardService _clipboardService +@inject ICurrencyService _currencyService @inject IDerivationOperations _derivationOperations @inject NavMenuState NavMenuState @inject IEncryptionService _encryptionService @inject IClipboardService ClipboardService - - @inherits BaseComponent Wallet and balances - - + +
- +
@@ -57,13 +54,12 @@ @if (!hasWallet) { -
- + No Wallet Found
@@ -75,7 +71,7 @@
Create Wallet
@@ -85,7 +81,7 @@
Recover Wallet
@@ -103,7 +99,7 @@ - } - @code { - private bool balanceSpinner = false; - private bool createWalletSpinner = false; - private bool testCoinsSpinner = false; - private bool sendLoadSpinner = false; - private bool sendConfirmSpinner = false; + private bool balanceSpinner; + private bool createWalletSpinner; + private bool testCoinsSpinner; + private bool sendLoadSpinner; + private bool sendConfirmSpinner; private string? showWalletWords; private string? showWalletWordsPassphrase; @@ -698,7 +702,7 @@ else private string passwordInputType = "password"; private string passwordToggleText = "Show"; - private bool isNewWallet = false; + private bool isNewWallet; private string createWalletTitle = "Create Wallet"; private string createWalletDescription = "Generate a new wallet"; @@ -707,10 +711,10 @@ else private bool walletWordsModal; private bool walletWordsCreateModal; - private bool isShowExtraWord = false; + private bool isShowExtraWord; - private bool backupConfirmation = false; - private bool showBackupConfirmationError = false; + private bool backupConfirmation; + private bool showBackupConfirmationError; private int activeTab = 1; @@ -719,31 +723,31 @@ else private int FeePosition = 1; private SendInfo _sendInfo = new(); - private AccountBalanceInfo accountBalanceInfo = new AccountBalanceInfo(); + private readonly AccountBalanceInfo accountBalanceInfo = new(); - private FeeEstimations FeeEstimations = new(); + private readonly FeeEstimations FeeEstimations = new(); // Max index for the range input - private int FeeMin = 1; + private readonly int FeeMin = 1; private int _feeMax = 3; DateTime _lastFeeRefresh = DateTime.MinValue; ShowQrCode showQrCode; - Dictionary collapseStates = new Dictionary(); - + readonly Dictionary collapseStates = new(); + private string btcBalanceInUsd; + private readonly Dictionary fiatValues = new(); protected override async Task OnInitializedAsync() { - - if (hasWallet) { var accountInfo = storage.GetAccountInfo(network.Name); var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); - accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInfo); + btcBalanceInUsd = await GetBtcValueInPreferredCurrency(Money.Satoshis(accountBalanceInfo.TotalBalance).ToUnit(MoneyUnit.BTC)); + await PrecomputeFiatValues(); } await base.OnInitializedAsync(); @@ -768,7 +772,7 @@ else storage.SetAccountInfo(network.Name, accountInfo); // lets log the entire account class to see what has changed - Logger.LogInformation(System.Text.Json.JsonSerializer.Serialize(accountInfo, new JsonSerializerOptions { WriteIndented = true })); + Logger.LogInformation(JsonSerializer.Serialize(accountInfo, new JsonSerializerOptions { WriteIndented = true })); var utxos = accountInfo.AllUtxos() .Select(x => x.outpoint.ToString()).ToList(); @@ -835,6 +839,7 @@ else notificationComponent.ShowErrorMessage("New wallet password is null or empty"); return; } + if (!backupConfirmation) { showBackupConfirmationError = true; @@ -847,8 +852,7 @@ else try { - - WalletWords data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase }; + var data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase }; var accountInfo = _walletOperations.BuildAccountInfoForWalletWords(data); await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); @@ -859,7 +863,7 @@ else accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, new List()); // pre-derive the angor wallet keys - FounderKeyCollection founderKeyCollection = _derivationOperations.DeriveProjectKeys(data, _networkConfiguration.GetAngorKey()); + var founderKeyCollection = _derivationOperations.DeriveProjectKeys(data, _networkConfiguration.GetAngorKey()); _walletStorage.SetFounderKeys(founderKeyCollection); hasWallet = _walletStorage.HasWallet(); @@ -1153,7 +1157,7 @@ else if (selected != null) { - if (int.TryParse(selected, out int res)) + if (int.TryParse(selected, out var res)) { if (res <= FeeEstimations.Fees.Count) { @@ -1201,7 +1205,6 @@ else unconfirmedInboundFunds.Add(new UtxoData { PendingSpent = true, address = receiveAddress, value = trx.Outputs.FirstOrDefault()?.Value.Satoshi ?? Money.Coins(50).Satoshi, outpoint = new Outpoint(trx.GetHash().ToString(), 0) }); _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInboundFunds); accountBalanceInfo.UpdateAccountBalanceInfo(accountBalanceInfo.AccountInfo, unconfirmedInboundFunds); - } catch (Exception e) { @@ -1255,4 +1258,37 @@ else { isShowExtraWord = !isShowExtraWord; } -} + + public async Task> GetBtcValuesInPreferredCurrency(params decimal[] btcBalances) + { + return await _currencyService.GetBtcValuesInPreferredCurrency(btcBalances); + } + + public async Task GetBtcValueInPreferredCurrency(decimal btcBalance) + { + var results = await GetBtcValuesInPreferredCurrency(btcBalance); + return results.FirstOrDefault() ?? "Error fetching value"; + } + + + private async Task PrecomputeFiatValues() + { + fiatValues.Clear(); + + var btcBalance = Money.Satoshis(accountBalanceInfo.TotalBalance).ToUnit(MoneyUnit.BTC); + + var fiatValuesList = await _currencyService.GetBtcValuesInPreferredCurrency(btcBalance); + + if (fiatValuesList == null || !fiatValuesList.Any()) + { + Logger.LogError("Failed to fetch the fiat value for the total balance."); + return; + } + + fiatValues["TotalBalance"] = fiatValuesList.First(); + + Logger.LogInformation($"Fiat value for total balance: {fiatValuesList.First()}"); + } + + +} \ No newline at end of file diff --git a/src/Angor/Client/Program.cs b/src/Angor/Client/Program.cs index 9a60002e..7c9ec618 100644 --- a/src/Angor/Client/Program.cs +++ b/src/Angor/Client/Program.cs @@ -24,15 +24,17 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddTransient(); -builder.Services.AddTransient (); +builder.Services.AddTransient(); builder.Services.AddTransient(); -builder.Services.AddTransient (); +builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -50,10 +52,10 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); -await builder.Build().RunAsync(); +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/src/Angor/Client/Services/CurrencyRateService.cs b/src/Angor/Client/Services/CurrencyRateService.cs new file mode 100644 index 00000000..0af879c3 --- /dev/null +++ b/src/Angor/Client/Services/CurrencyRateService.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using Angor.Client.Storage; + +public class CurrencyRateService : ICurrencyRateService +{ + private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(10); + private readonly ICacheStorage _cacheStorage; + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + private const string ApiUrl = "https://mempool.space/api/v1/prices"; + + public CurrencyRateService(HttpClient httpClient, ILogger logger, ICacheStorage cacheStorage) + { + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _cacheStorage = cacheStorage ?? throw new ArgumentNullException(nameof(cacheStorage)); + } + + public async Task GetBtcToCurrencyRate(string currencyCode) + { + if (string.IsNullOrWhiteSpace(currencyCode)) + throw new ArgumentException("Currency code must not be null or empty.", nameof(currencyCode)); + + var preferredCurrency = currencyCode.ToUpper(); + + // Check if the rate is cached and still valid + var cachedRate = GetCachedRate(preferredCurrency); + if (cachedRate.HasValue) + { + _logger.LogInformation($"Using cached rate for {preferredCurrency}: {cachedRate.Value}"); + return cachedRate.Value; + } + + // Fetch the latest rate from API + var latestRate = await FetchRateFromApi(preferredCurrency); + CacheRate(preferredCurrency, latestRate); + + return latestRate; + } + + private decimal? GetCachedRate(string currencyCode) + { + var cachedEntry = _cacheStorage.GetCurrencyRate(currencyCode); + if (cachedEntry != null && DateTime.UtcNow - cachedEntry.Timestamp < _cacheDuration) + { + return cachedEntry.Rate; + } + + return null; + } + + private async Task FetchRateFromApi(string currencyCode) + { + try + { + var response = await _httpClient.GetAsync(ApiUrl); + response.EnsureSuccessStatusCode(); + + var jsonString = await response.Content.ReadAsStringAsync(); + _logger.LogInformation($"API Response: {jsonString}"); + + var data = JsonSerializer.Deserialize>(jsonString); + if (data != null && data.TryGetValue(currencyCode, out var rateElement)) + { + return rateElement.GetDecimal(); + } + + throw new KeyNotFoundException($"Currency '{currencyCode}' not found in the API response."); + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Error while fetching data from API."); + throw new Exception("Failed to fetch BTC rate from the API.", ex); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Error parsing the API response."); + throw new Exception("Invalid response format from the API.", ex); + } + } + + private void CacheRate(string currencyCode, decimal rate) + { + _cacheStorage.SetCurrencyRate(currencyCode, new RateCacheEntry + { + Rate = rate, + Timestamp = DateTime.UtcNow + }); + _logger.LogInformation($"Cached new rate for {currencyCode}: {rate}"); + } +} + +public class RateCacheEntry +{ + public decimal Rate { get; set; } + public DateTime Timestamp { get; set; } +} diff --git a/src/Angor/Client/Services/CurrencyService.cs b/src/Angor/Client/Services/CurrencyService.cs new file mode 100644 index 00000000..40bad83e --- /dev/null +++ b/src/Angor/Client/Services/CurrencyService.cs @@ -0,0 +1,118 @@ +using System.Globalization; +using Angor.Client.Storage; +using Angor.Shared; + +public class CurrencyService : ICurrencyService +{ + private readonly ICurrencyRateService _currencyRateService; + private readonly ILogger _logger; + private readonly INetworkConfiguration _networkConfiguration; + private readonly IClientStorage _storage; + + public CurrencyService( + ICurrencyRateService currencyRateService, + IClientStorage storage, + ILogger logger, + INetworkConfiguration network) + { + _currencyRateService = currencyRateService ?? throw new ArgumentNullException(nameof(currencyRateService)); + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _networkConfiguration = network ?? throw new ArgumentNullException(nameof(network)); + } + + public string GetCurrencySymbol(string currencyCode) + { + return currencyCode.ToUpper() switch + { + "USD" => "$", + "GBP" => "£", + "EUR" => "€", + "CAD" => "C$", + "CHF" => "CHF", + "AUD" => "A$", + "JPY" => "¥", + _ => currencyCode.ToUpper() // Return code if no symbol found + }; + } + + public async Task> GetBtcValuesInPreferredCurrency(params decimal[] btcBalances) + { + if (btcBalances == null || btcBalances.Length == 0) + { + _logger.LogWarning("No BTC balances provided."); + return new List { "No balances provided" }; + } + + // Check if the current network is Bitcoin (BTC) + if (!_networkConfiguration.GetNetwork().CoinTicker.Equals("BTC", StringComparison.OrdinalIgnoreCase)) + { + return GenerateEmptyStringList(btcBalances.Length); + } + + string currencyCode; + decimal rate; + string currencySymbol; + CultureInfo cultureInfo; + + try + { + currencyCode = _storage.GetCurrencyDisplaySetting(); + + // If preferred currency is BTC, return empty strings + if (currencyCode.Equals("BTC", StringComparison.OrdinalIgnoreCase)) + { + return GenerateEmptyStringList(btcBalances.Length); + } + + // Get exchange rate, symbol, and culture info + rate = await _currencyRateService.GetBtcToCurrencyRate(currencyCode); + currencySymbol = GetCurrencySymbol(currencyCode); + cultureInfo = new CultureInfo(GetCultureCode(currencyCode)); + } + catch (Exception ex) + { + _logger.LogError($"Error fetching currency settings or rates: {ex.Message}"); + return new List { "Error fetching settings or rates" }; + } + + try + { + return btcBalances + .AsParallel() + .Select(btcBalance => CalculateFormattedValue(btcBalance, rate, currencySymbol, cultureInfo)) + .ToList(); + } + catch (Exception ex) + { + _logger.LogError($"Error calculating BTC values: {ex.Message}"); + return new List { "Error calculating values" }; + } + } + + private string CalculateFormattedValue(decimal btcBalance, decimal rate, string currencySymbol, CultureInfo cultureInfo) + { + var value = btcBalance * rate; + return value.ToString("C2", cultureInfo).Replace(cultureInfo.NumberFormat.CurrencySymbol, currencySymbol); + } + + private static string GetCultureCode(string currencyCode) + { + return currencyCode.ToUpper() switch + { + "USD" => "en-US", + "GBP" => "en-GB", + "EUR" => "fr-FR", + "CAD" => "en-CA", + "CHF" => "de-CH", + "AUD" => "en-AU", + "JPY" => "ja-JP", + _ => "en-US" // Default to US culture if no match found + }; + } + + private static List GenerateEmptyStringList(int length) + { + return Enumerable.Repeat(string.Empty, length).ToList(); + } +} diff --git a/src/Angor/Client/Services/ICurrencyRateService.cs b/src/Angor/Client/Services/ICurrencyRateService.cs new file mode 100644 index 00000000..b3db8da1 --- /dev/null +++ b/src/Angor/Client/Services/ICurrencyRateService.cs @@ -0,0 +1,4 @@ +public interface ICurrencyRateService +{ + Task GetBtcToCurrencyRate(string currencyCode); +} \ No newline at end of file diff --git a/src/Angor/Client/Services/ICurrencyService.cs b/src/Angor/Client/Services/ICurrencyService.cs new file mode 100644 index 00000000..2fe91596 --- /dev/null +++ b/src/Angor/Client/Services/ICurrencyService.cs @@ -0,0 +1,5 @@ +public interface ICurrencyService +{ + Task> GetBtcValuesInPreferredCurrency(params decimal[] btcBalances); + string GetCurrencySymbol(string currencyCode); +} \ No newline at end of file diff --git a/src/Angor/Client/Storage/ClientStorage.cs b/src/Angor/Client/Storage/ClientStorage.cs index 68f57d5e..b697682c 100644 --- a/src/Angor/Client/Storage/ClientStorage.cs +++ b/src/Angor/Client/Storage/ClientStorage.cs @@ -10,6 +10,8 @@ public class ClientStorage : IClientStorage, INetworkStorage { private readonly ISyncLocalStorageService _storage; + private const string CurrencyDisplaySettingKey = "currencyDisplaySetting"; + private const string utxoKey = "utxo:{0}"; public ClientStorage(ISyncLocalStorageService storage) { @@ -224,4 +226,14 @@ public string GetNostrPublicKeyPerProject(string projectId) { return _storage.GetItem($"project:{projectId}:nostrKey"); } + + public string GetCurrencyDisplaySetting() + { + return _storage.GetItem(CurrencyDisplaySettingKey) ?? "BTC"; + } + + public void SetCurrencyDisplaySetting(string setting) + { + _storage.SetItem(CurrencyDisplaySettingKey, setting); + } } \ No newline at end of file diff --git a/src/Angor/Client/Storage/ICacheStorage.cs b/src/Angor/Client/Storage/ICacheStorage.cs index 59893751..c2929513 100644 --- a/src/Angor/Client/Storage/ICacheStorage.cs +++ b/src/Angor/Client/Storage/ICacheStorage.cs @@ -18,5 +18,7 @@ public interface ICacheStorage void SetUnconfirmedInboundFunds(List unconfirmedInfo); void SetUnconfirmedOutboundFunds(List unconfirmedInfo); void DeleteUnconfirmedInfo(); + void SetCurrencyRate(string currencyCode, RateCacheEntry rateCacheEntry); + RateCacheEntry? GetCurrencyRate(string currencyCode); void WipeSession(); } \ No newline at end of file diff --git a/src/Angor/Client/Storage/IClientStorage.cs b/src/Angor/Client/Storage/IClientStorage.cs index 45fb8d26..8eb5fa8a 100644 --- a/src/Angor/Client/Storage/IClientStorage.cs +++ b/src/Angor/Client/Storage/IClientStorage.cs @@ -33,5 +33,8 @@ public interface IClientStorage SettingsInfo GetSettingsInfo(); void SetSettingsInfo(SettingsInfo settingsInfo); void WipeStorage(); + + string GetCurrencyDisplaySetting(); + void SetCurrencyDisplaySetting(string setting); } } diff --git a/src/Angor/Client/Storage/LocalSessionStorage.cs b/src/Angor/Client/Storage/LocalSessionStorage.cs index 7e500548..41cfc82c 100644 --- a/src/Angor/Client/Storage/LocalSessionStorage.cs +++ b/src/Angor/Client/Storage/LocalSessionStorage.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Angor.Client.Models; using Angor.Shared.Models; using Angor.Shared.Services; @@ -7,10 +8,9 @@ namespace Angor.Client.Storage; public class LocalSessionStorage : ICacheStorage { - private ISyncSessionStorageService _sessionStorageService; - private const string BrowseIndexerData = "subscriptions"; private const string NostrPubKeyMapping = "NostrPubKeyMapping"; + private readonly ISyncSessionStorageService _sessionStorageService; public LocalSessionStorage(ISyncSessionStorageService sessionStorageService) { @@ -19,7 +19,7 @@ public LocalSessionStorage(ISyncSessionStorageService sessionStorageService) public void StoreProject(Project project) { - _sessionStorageService.SetItem(project.ProjectInfo.ProjectIdentifier,project); + _sessionStorageService.SetItem(project.ProjectInfo.ProjectIdentifier, project); } public ProjectMetadata? GetProjectMetadataByPubkey(string pubkey) @@ -27,11 +27,6 @@ public void StoreProject(Project project) return _sessionStorageService.GetItem(pubkey); } - public void StoreProjectMetadata(string pubkey, ProjectMetadata projectMetadata) - { - _sessionStorageService.SetItem(pubkey, projectMetadata); - } - public bool IsProjectMetadataStorageByPubkey(string pubkey) { return _sessionStorageService.ContainKey(pubkey); @@ -42,26 +37,6 @@ public bool IsProjectMetadataStorageByPubkey(string pubkey) return _sessionStorageService.GetItem(projectId); } - public void AddProjectIdToNostrPubKeyMapping(string npub, string projectId) - { - var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); - - dictionary ??= new (); - - dictionary.TryAdd(npub, projectId); - - _sessionStorageService.SetItem(NostrPubKeyMapping,dictionary); - } - - public string? GetProjectIdByNostrPubKey(string npub) - { - var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); - - if (dictionary is null) return null; - - return dictionary.TryGetValue(npub, out var value) ? value : null; - } - public bool IsProjectInStorageById(string projectId) { return _sessionStorageService.ContainKey(projectId); @@ -74,17 +49,17 @@ public bool IsProjectInStorageById(string projectId) public void SetProjectIndexerData(List list) { - _sessionStorageService.SetItem(BrowseIndexerData,list); + _sessionStorageService.SetItem(BrowseIndexerData, list); } public List GetUnconfirmedInboundFunds() { - return _sessionStorageService.GetItem>("unconfirmed-inbound") ?? new (); + return _sessionStorageService.GetItem>("unconfirmed-inbound") ?? new List(); } public List GetUnconfirmedOutboundFunds() { - return _sessionStorageService.GetItem>("unconfirmed-outbound") ?? new(); + return _sessionStorageService.GetItem>("unconfirmed-outbound") ?? new List(); } public void SetUnconfirmedInboundFunds(List unconfirmedInfo) @@ -96,19 +71,66 @@ public void SetUnconfirmedOutboundFunds(List unconfirmedInfo) { _sessionStorageService.SetItem("unconfirmed-outbound", unconfirmedInfo); } - - public List GetNamesOfCommunicatorsThatReceivedEose(string subscriptionName) - { - return _sessionStorageService.GetItem>("Eose" + subscriptionName); - } public void DeleteUnconfirmedInfo() { _sessionStorageService.RemoveItem("unconfirmed-info"); } + public void SetCurrencyRate(string currencyCode, RateCacheEntry rateCacheEntry) + { + var cacheJson = JsonSerializer.Serialize(rateCacheEntry); + _sessionStorageService.SetItem(currencyCode, cacheJson); + } + + public RateCacheEntry? GetCurrencyRate(string currencyCode) + { + var cacheJson = _sessionStorageService.GetItem(currencyCode); + + if (string.IsNullOrEmpty(cacheJson)) return null; + + try + { + return JsonSerializer.Deserialize(cacheJson); + } + catch (JsonException ex) + { + return null; + } + } + public void WipeSession() { _sessionStorageService.Clear(); } + + public void StoreProjectMetadata(string pubkey, ProjectMetadata projectMetadata) + { + _sessionStorageService.SetItem(pubkey, projectMetadata); + } + + public void AddProjectIdToNostrPubKeyMapping(string npub, string projectId) + { + var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); + + dictionary ??= new Dictionary(); + + dictionary.TryAdd(npub, projectId); + + _sessionStorageService.SetItem(NostrPubKeyMapping, dictionary); + } + + public string? GetProjectIdByNostrPubKey(string npub) + { + var dictionary = _sessionStorageService.GetItem>(NostrPubKeyMapping); + + if (dictionary is null) return null; + + return dictionary.TryGetValue(npub, out var value) ? value : null; + } + + public List GetNamesOfCommunicatorsThatReceivedEose(string subscriptionName) + { + return _sessionStorageService.GetItem>("Eose" + subscriptionName); + } } \ No newline at end of file diff --git a/src/Angor/Client/wwwroot/assets/icons/currency.svg b/src/Angor/Client/wwwroot/assets/icons/currency.svg new file mode 100644 index 00000000..401ca9d8 --- /dev/null +++ b/src/Angor/Client/wwwroot/assets/icons/currency.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Angor/Server/Angor.Server.csproj b/src/Angor/Server/Angor.Server.csproj index 3634b23f..42eff20f 100644 --- a/src/Angor/Server/Angor.Server.csproj +++ b/src/Angor/Server/Angor.Server.csproj @@ -10,7 +10,7 @@ - +