using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; using Managing.Domain.Users; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; namespace Managing.Application.Accounts; public class AccountService : IAccountService { private readonly IAccountRepository _accountRepository; private readonly IExchangeService _exchangeService; private readonly IEvmManager _evmManager; private readonly ICacheService _cacheService; private readonly IWeb3ProxyService _web3ProxyService; private readonly IUserRepository _userRepository; private readonly ILogger _logger; public AccountService( IAccountRepository accountRepository, ILogger logger, IExchangeService exchangeService, IEvmManager evmManager, ICacheService cacheService, IWeb3ProxyService web3ProxyService, IUserRepository userRepository) { _accountRepository = accountRepository; _logger = logger; _exchangeService = exchangeService; _evmManager = evmManager; _cacheService = cacheService; _web3ProxyService = web3ProxyService; _userRepository = userRepository; } public async Task CreateAccount(User user, Account request) { var account = await _accountRepository.GetAccountByNameAsync(request.Name); if (account != null) { throw new Exception($"Account {request.Name} alreary exist"); } else { request.User = user; if (request.Exchange == TradingExchanges.Evm && request.Type == AccountType.Trader) { var keys = _evmManager.GenerateAddress(); request.Key = keys.Key; request.Secret = keys.Secret; } else if (request.Exchange == TradingExchanges.Evm && request.Type == AccountType.Privy) { if (string.IsNullOrEmpty(request.Key)) { // No key provided, create new privy embedded wallet. // TODO : Fix it to create privy wallet var privyClient = await _evmManager.CreatePrivyWallet(); request.Key = privyClient.Address; request.Secret = privyClient.Id; } else { request.Key = request.Key; // Address request.Secret = request.Secret; // Privy wallet id } } else { request.Key = request.Key; request.Secret = request.Secret; } await _accountRepository.InsertAccountAsync(request); } return request; } public bool DeleteAccount(User user, string name) { try { _accountRepository.DeleteAccountByName(name); return true; } catch (Exception ex) { _logger.LogError(ex.Message); return false; } } public async Task GetAccount(string name, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByNameAsync(name); ManageProperties(hideSecrets, getBalance, account); account.User = await _userRepository.GetUserByNameAsync(account.User.Name); return account; } public async Task GetAccountByKey(string key, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByKeyAsync(key); ManageProperties(hideSecrets, getBalance, account); return account; } public async Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByNameAsync(name); ManageProperties(hideSecrets, getBalance, account); return account; } public IEnumerable GetAccounts(bool hideSecrets, bool getBalance) { var result = _accountRepository.GetAccounts(); var accounts = new List(); foreach (var account in result) { ManageProperties(hideSecrets, getBalance, account); accounts.Add(account); } return accounts; } public IEnumerable GetAccountsByUser(User user, bool hideSecrets = true) { var cacheKey = $"user-account-{user.Name}"; return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); }, TimeSpan.FromMinutes(5)); } private IEnumerable GetAccounts(User user, bool hideSecrets, bool getBalance) { var result = _accountRepository.GetAccounts(); var accounts = new List(); foreach (var account in result.Where(a => a.User.Name == user.Name)) { ManageProperties(hideSecrets, getBalance, account); accounts.Add(account); } return accounts; } public IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets) { var cacheKey = $"user-account-balance-{user.Name}"; var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); }, TimeSpan.FromHours(3)); return accounts; } public async Task GetGmxClaimableSummaryAsync(User user, string accountName) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); if (account == null) { throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'"); } // Ensure the account has a valid address/key if (string.IsNullOrEmpty(account.Key)) { throw new ArgumentException($"Account '{accountName}' does not have a valid address"); } try { // Call the Web3ProxyService to get GMX claimable summary var infrastructureResponse = await _web3ProxyService.GetGmxClaimableSummaryAsync(account.Key); // Map from infrastructure model to domain model return infrastructureResponse; } catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException)) { _logger.LogError(ex, "Error getting GMX claimable summary for account {AccountName} and user {UserName}", accountName, user.Name); throw new InvalidOperationException($"Failed to get GMX claimable summary: {ex.Message}", ex); } } public async Task SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); if (account == null) { throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'"); } // Ensure the account has a valid address/key if (string.IsNullOrEmpty(account.Key)) { throw new ArgumentException($"Account '{accountName}' does not have a valid address"); } try { // Call the Web3ProxyService to swap GMX tokens var swapInfos = await _web3ProxyService.SwapGmxTokensAsync( account.Key, fromTicker, toTicker, amount, orderType, triggerRatio, allowedSlippage ); return swapInfos; } catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException)) { _logger.LogError(ex, "Error swapping GMX tokens for account {AccountName} and user {UserName}", accountName, user.Name); throw new InvalidOperationException($"Failed to swap GMX tokens: {ex.Message}", ex); } } public async Task SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); if (account == null) { throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'"); } // Ensure the account has a valid address/key if (string.IsNullOrEmpty(account.Key)) { throw new ArgumentException($"Account '{accountName}' does not have a valid address"); } // Validate recipient address if (string.IsNullOrEmpty(recipientAddress)) { throw new ArgumentException("Recipient address is required"); } // Validate amount if (amount <= 0) { throw new ArgumentException("Amount must be greater than zero"); } try { // Call the Web3ProxyService to send tokens var swapInfos = await _web3ProxyService.SendTokenAsync( account.Key, recipientAddress, ticker, amount, chainId ); return swapInfos; } catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException)) { _logger.LogError(ex, "Error sending tokens for account {AccountName} and user {UserName}", accountName, user.Name); throw new InvalidOperationException($"Failed to send tokens: {ex.Message}", ex); } } private void ManageProperties(bool hideSecrets, bool getBalance, Account account) { if (account != null) { if (getBalance) { try { account.Balances = _exchangeService.GetBalances(account).Result; } catch (Exception ex) { _logger.LogError(ex, $"Cannot get account {account.Name} balance"); } } if (hideSecrets) { account.Secret = ""; } } } }