using Managing.Application.Abstractions; using Managing.Application.Abstractions.Grains; using Managing.Application.Abstractions.Services; using Managing.Application.ManageBot.Commands; using Managing.Common; using Managing.Domain.Accounts; using MediatR; using static Managing.Common.Enums; namespace Managing.Application.ManageBot { public class StartBotCommandHandler : IRequestHandler { private readonly IAccountService _accountService; private readonly IGrainFactory _grainFactory; private readonly IBotService _botService; private readonly ITradingService _tradingService; public StartBotCommandHandler( IAccountService accountService, IGrainFactory grainFactory, IBotService botService, ITradingService tradingService) { _accountService = accountService; _grainFactory = grainFactory; _botService = botService; _tradingService = tradingService; } public async Task Handle(StartBotCommand request, CancellationToken cancellationToken) { // Validate the configuration if (request.Config == null) { throw new ArgumentException("Bot configuration is required"); } if (request.Config.Scenario == null || !request.Config.Scenario.Indicators.Any()) { throw new InvalidOperationException( "Scenario or indicators not loaded properly in constructor. This indicates a configuration error."); } if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) { throw new ArgumentException( $"Bot trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}"); } // Check if user already has a bot on this ticker var hasExistingBotOnTicker = await _botService.HasUserBotOnTickerAsync(request.User.Id, request.Config.Ticker); if (hasExistingBotOnTicker) { throw new InvalidOperationException( $"You already have a strategy running or saved on ticker {request.Config.Ticker}. " + "You cannot create multiple strategies on the same ticker."); } // Check if user already has a bot with this name var hasExistingBotWithName = await _botService.HasUserBotWithNameAsync(request.User.Id, request.Config.Name); if (hasExistingBotWithName) { throw new InvalidOperationException( $"You already have a strategy running or saved with the name '{request.Config.Name}'. " + "You cannot create multiple strategies with the same name."); } Account account; if (string.IsNullOrEmpty(request.Config.AccountName)) { // Fallback: Get the first account for the user var userAccounts = await _accountService.GetAccountsByUserAsync(request.User, true, true); var firstAccount = userAccounts.FirstOrDefault(); if (firstAccount == null) { throw new InvalidOperationException($"User '{request.User.Name}' has no accounts configured."); } account = firstAccount; } else { account = await _accountService.GetAccount(request.Config.AccountName, true, true); } // Check balances for EVM/GMX V2 accounts before starting if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2) { var balanceCheckResult = await _botService.CheckAccountBalancesAsync(account); if (!balanceCheckResult.IsSuccessful) { throw new InvalidOperationException(balanceCheckResult.Message); } } // Check GMX initialization status for GMX V2 accounts if (account.Exchange == TradingExchanges.GmxV2 && !account.IsGmxInitialized) { var initResult = await _tradingService.InitPrivyWallet(account.Key, TradingExchanges.GmxV2); if (!initResult.Success) { throw new InvalidOperationException($"Failed to initialize GMX wallet: {initResult.Error}"); } } Balance usdcBalance = null; // For other exchanges, keep the original USDC balance check if (account.Exchange != TradingExchanges.Evm && account.Exchange != TradingExchanges.GmxV2) { usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString()); if (usdcBalance == null || usdcBalance.Value < Constants.GMX.Config.MinimumPositionAmount || usdcBalance.Value < request.Config.BotTradingBalance) { throw new Exception( $"Account {request.Config.AccountName} has no USDC balance or not enough balance"); } } // Enforce allocation limit across all bots using the same account var availableAllocation = await _botService.GetAvailableAllocationUsdAsync(account, default); if (request.Config.BotTradingBalance > availableAllocation) { throw new InvalidOperationException( $"Insufficient available allocation. Requested: {request.Config.BotTradingBalance:F2} USDC, " + $"Balance : {usdcBalance?.Value:F2 ?? 0} Available: {availableAllocation:F2} USDC."); } try { var botGrain = _grainFactory.GetGrain(Guid.NewGuid()); await botGrain.CreateAsync(request.Config, request.User); // Only start the bot if createOnly is false if (!request.CreateOnly) { await botGrain.StartAsync(); } } catch (Exception ex) { throw new Exception($"Failed to start bot: {ex.Message}, {ex.StackTrace}"); } return request.CreateOnly ? BotStatus.Saved : BotStatus.Running; } } }