* Start price reminder grain * Add config and init grain at startup * Save init wallet when already init
386 lines
13 KiB
C#
386 lines
13 KiB
C#
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<AccountService> _logger;
|
|
|
|
public AccountService(
|
|
IAccountRepository accountRepository,
|
|
ILogger<AccountService> 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<Account> CreateAccount(User user, Account account)
|
|
{
|
|
var a = await _accountRepository.GetAccountByNameAsync(account.Name);
|
|
|
|
if (a != null)
|
|
{
|
|
throw new Exception($"Account {account.Name} alreary exist");
|
|
}
|
|
else
|
|
{
|
|
account.User = user;
|
|
|
|
if (account.Exchange == TradingExchanges.Evm
|
|
&& account.Type == AccountType.Trader)
|
|
{
|
|
var keys = _evmManager.GenerateAddress();
|
|
account.Key = keys.Key;
|
|
account.Secret = keys.Secret;
|
|
}
|
|
else if (account.Exchange == TradingExchanges.Evm
|
|
&& account.Type == AccountType.Privy)
|
|
{
|
|
if (string.IsNullOrEmpty(account.Key))
|
|
{
|
|
// No key provided, create new privy embedded wallet.
|
|
// TODO : Fix it to create privy wallet
|
|
var privyClient = await _evmManager.CreatePrivyWallet();
|
|
account.Key = privyClient.Address;
|
|
account.Secret = privyClient.Id;
|
|
}
|
|
else
|
|
{
|
|
account.Key = account.Key; // Address
|
|
account.Secret = account.Secret; // Privy wallet id
|
|
}
|
|
}
|
|
else
|
|
{
|
|
account.Key = account.Key;
|
|
account.Secret = account.Secret;
|
|
}
|
|
|
|
await _accountRepository.InsertAccountAsync(account);
|
|
}
|
|
|
|
return account;
|
|
}
|
|
|
|
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<Account> GetAccount(string name, bool hideSecrets, bool getBalance)
|
|
{
|
|
var account = await _accountRepository.GetAccountByNameAsync(name);
|
|
|
|
if (account == null)
|
|
{
|
|
throw new ArgumentException($"Account '{name}' not found");
|
|
}
|
|
|
|
await ManagePropertiesAsync(hideSecrets, getBalance, account);
|
|
|
|
if (account.User == null && account.User != null)
|
|
{
|
|
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
|
|
}
|
|
|
|
return account;
|
|
}
|
|
|
|
public async Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance)
|
|
{
|
|
var account = await _accountRepository.GetAccountByKeyAsync(key);
|
|
await ManagePropertiesAsync(hideSecrets, getBalance, account);
|
|
|
|
return account;
|
|
}
|
|
|
|
public async Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance)
|
|
{
|
|
var account = await _accountRepository.GetAccountByNameAsync(name);
|
|
await ManagePropertiesAsync(hideSecrets, getBalance, account);
|
|
|
|
return account;
|
|
}
|
|
|
|
public async Task<Account> GetAccountByAccountName(string accountName, bool hideSecrets = true,
|
|
bool getBalance = false)
|
|
{
|
|
var account = await _accountRepository.GetAccountByNameAsync(accountName);
|
|
|
|
if (account != null)
|
|
{
|
|
await ManagePropertiesAsync(hideSecrets, getBalance, account);
|
|
if (account.User != null)
|
|
{
|
|
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
|
|
}
|
|
}
|
|
|
|
return account;
|
|
}
|
|
|
|
public async Task<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance)
|
|
{
|
|
return await GetAccountsAsync(hideSecrets, getBalance);
|
|
}
|
|
|
|
public async Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance)
|
|
{
|
|
var result = await _accountRepository.GetAccountsAsync();
|
|
var accounts = new List<Account>();
|
|
|
|
foreach (var account in result)
|
|
{
|
|
await ManagePropertiesAsync(hideSecrets, getBalance, account);
|
|
accounts.Add(account);
|
|
}
|
|
|
|
return accounts;
|
|
}
|
|
|
|
public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
|
|
{
|
|
return GetAccountsByUserAsync(user, hideSecrets).Result;
|
|
}
|
|
|
|
public async Task<IEnumerable<Account>> GetAccountsByUserAsync(User user, bool hideSecrets = true,
|
|
bool getBalance = false)
|
|
{
|
|
var cacheKey = $"user-account-{user.Name}";
|
|
|
|
// For now, we'll get fresh data since caching async operations requires more complex logic
|
|
// This can be optimized later with proper async caching
|
|
return await GetAccountsAsync(user, hideSecrets, getBalance);
|
|
}
|
|
|
|
private async Task<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)
|
|
{
|
|
var result = await _accountRepository.GetAccountsAsync();
|
|
var accounts = new List<Account>();
|
|
|
|
foreach (var account in result.Where(a => a.User.Name == user.Name))
|
|
{
|
|
await ManagePropertiesAsync(hideSecrets, getBalance, account);
|
|
accounts.Add(account);
|
|
}
|
|
|
|
return accounts;
|
|
}
|
|
|
|
public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
|
|
{
|
|
return GetAccountsBalancesByUserAsync(user, hideSecrets).Result;
|
|
}
|
|
|
|
public async Task<IEnumerable<Account>> GetAccountsBalancesByUserAsync(User user, bool hideSecrets)
|
|
{
|
|
var cacheKey = $"user-account-balance-{user.Name}";
|
|
|
|
// For now, get fresh data since caching async operations requires more complex logic
|
|
// This can be optimized later with proper async caching
|
|
return await GetAccountsAsync(user, hideSecrets, true);
|
|
}
|
|
|
|
public async Task<GmxClaimableSummary> 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<SwapInfos> 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<SwapInfos> 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);
|
|
}
|
|
}
|
|
|
|
public async Task<List<ExchangeApprovalStatus>> GetExchangeApprovalStatusAsync(User user)
|
|
{
|
|
var accounts = await GetAccountsByUserAsync(user, hideSecrets: true, getBalance: false);
|
|
|
|
var exchangeStatuses = new List<ExchangeApprovalStatus>();
|
|
|
|
foreach (var account in accounts)
|
|
{
|
|
exchangeStatuses.Add(new ExchangeApprovalStatus
|
|
{
|
|
Exchange = TradingExchanges.GmxV2,
|
|
IsApproved = account.IsGmxInitialized
|
|
});
|
|
}
|
|
|
|
// Future: Add other exchanges here when supported
|
|
// e.g.:
|
|
// var hasEvmInitialized = accounts.Any(account =>
|
|
// account.Exchange == TradingExchanges.Evm && account.IsGmxInitialized);
|
|
// exchangeStatuses.Add(new ExchangeApprovalStatus
|
|
// {
|
|
// Exchange = TradingExchanges.Evm,
|
|
// IsApproved = hasEvmInitialized
|
|
// });
|
|
|
|
return exchangeStatuses;
|
|
}
|
|
|
|
private async Task ManagePropertiesAsync(bool hideSecrets, bool getBalance, Account account)
|
|
{
|
|
if (account != null)
|
|
{
|
|
if (getBalance)
|
|
{
|
|
try
|
|
{
|
|
account.Balances = await _exchangeService.GetBalances(account);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, $"Cannot get account {account.Name} balance");
|
|
}
|
|
}
|
|
|
|
if (hideSecrets)
|
|
{
|
|
account.Secret = "";
|
|
}
|
|
}
|
|
}
|
|
} |