using System.Net; using System.Net.Sockets; using System.Reflection; using Discord.Commands; using Discord.WebSocket; using FluentValidation; using Managing.Application; using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Accounts; using Managing.Application.Agents; using Managing.Application.Backtests; using Managing.Application.ManageBot; using Managing.Application.ManageBot.Commands; using Managing.Application.MoneyManagements; using Managing.Application.Scenarios; using Managing.Application.Shared; using Managing.Application.Shared.Behaviours; using Managing.Application.Synth; using Managing.Application.Trading; using Managing.Application.Trading.Commands; using Managing.Application.Trading.Handlers; using Managing.Application.Users; using Managing.Application.Workers; using Managing.Domain.Trades; using Managing.Infrastructure.Database.PostgreSql; using Managing.Infrastructure.Databases; using Managing.Infrastructure.Databases.InfluxDb; using Managing.Infrastructure.Databases.InfluxDb.Abstractions; using Managing.Infrastructure.Databases.InfluxDb.Models; using Managing.Infrastructure.Databases.PostgreSql; using Managing.Infrastructure.Databases.PostgreSql.Configurations; using Managing.Infrastructure.Evm; using Managing.Infrastructure.Evm.Abstractions; using Managing.Infrastructure.Evm.Models.Privy; using Managing.Infrastructure.Evm.Services; using Managing.Infrastructure.Evm.Subgraphs; using Managing.Infrastructure.Exchanges; using Managing.Infrastructure.Exchanges.Abstractions; using Managing.Infrastructure.Exchanges.Exchanges; using Managing.Infrastructure.Messengers.Discord; using Managing.Infrastructure.Storage; using MediatR; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans.Configuration; namespace Managing.Bootstrap; public static class ApiBootstrap { private static readonly Assembly ApplicationAssembly = typeof(StartBotCommand).GetTypeInfo().Assembly; public static IServiceCollection RegisterApiDependencies(this IServiceCollection services, IConfiguration configuration) { services.Configure(configuration.GetSection("Web3Proxy")); return services .AddApplication() .AddInfrastructure(configuration) .AddWorkers(configuration) .AddFluentValidation() .AddMediatR() ; } // Note: IClusterClient is automatically available in co-hosting scenarios // through IGrainFactory. Services should inject IGrainFactory instead of IClusterClient // to avoid circular dependency issues during DI container construction. public static IHostBuilder ConfigureOrleans(this IHostBuilder hostBuilder, IConfiguration configuration, bool isProduction) { // Check if Orleans grains should be active using root-level configuration var runOrleansGrains = configuration.GetValue("RunOrleansGrains", true); // Check environment variable as override var runOrleansGrainsEnv = Environment.GetEnvironmentVariable("RUN_ORLEANS_GRAINS"); if (!string.IsNullOrEmpty(runOrleansGrainsEnv) && bool.TryParse(runOrleansGrainsEnv, out var runOrleansGrainsFromEnv)) { runOrleansGrains = runOrleansGrainsFromEnv; } // Allow disabling Orleans clustering entirely in case of issues var disableOrleansClusteringEnv = Environment.GetEnvironmentVariable("DISABLE_ORLEANS_CLUSTERING"); var disableOrleansClustering = !string.IsNullOrEmpty(disableOrleansClusteringEnv) && bool.TryParse(disableOrleansClusteringEnv, out var disabled) && disabled; // Get TASK_SLOT for multi-instance configuration var taskSlotEnv = Environment.GetEnvironmentVariable("TASK_SLOT"); var taskSlot = 1; // Default to 1 if not provided if (!string.IsNullOrEmpty(taskSlotEnv) && int.TryParse(taskSlotEnv, out var parsedTaskSlot)) { taskSlot = parsedTaskSlot; } // Calculate unique ports based on task slot var siloPort = 11111 + (taskSlot - 1) * 10; // 11111, 11121, 11131, etc. var gatewayPort = 30000 + (taskSlot - 1) * 10; // 30000, 30010, 30020, etc. var dashboardPort = 9999 + (taskSlot - 1); // 9999, 10000, 10001, etc. // Get hostname for clustering - prioritize external IP for multi-server setups var hostname = Environment.GetEnvironmentVariable("CAPROVER_SERVER_IP") ?? // CapRover server IP Environment.GetEnvironmentVariable("EXTERNAL_IP") ?? // Custom external IP Environment.GetEnvironmentVariable("HOSTNAME") ?? // Container hostname Environment.GetEnvironmentVariable("COMPUTERNAME") ?? // Windows hostname "localhost"; // Parse the hostname to IP address for Orleans IPAddress advertisedIP; if (IPAddress.TryParse(hostname, out advertisedIP)) { // hostname is already an IP address } else { // Try to resolve hostname to IP address try { var hostEntry = Dns.GetHostEntry(hostname); advertisedIP = hostEntry.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork) ?? hostEntry.AddressList.FirstOrDefault(); } catch { // Fallback to localhost if resolution fails advertisedIP = IPAddress.Loopback; } } var postgreSqlConnectionString = configuration.GetSection("PostgreSql")["Orleans"]; Console.WriteLine($"Task Slot: {taskSlot}"); Console.WriteLine($"Hostname: {hostname}"); Console.WriteLine($"Advertised IP: {advertisedIP}"); Console.WriteLine($"Silo port: {siloPort}"); Console.WriteLine($"Gateway port: {gatewayPort}"); Console.WriteLine($"Dashboard port: {dashboardPort}"); return hostBuilder.UseOrleans(siloBuilder => { // Configure clustering with improved networking or use localhost clustering if disabled if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString)) { siloBuilder .UseAdoNetClustering(options => { options.ConnectionString = postgreSqlConnectionString; options.Invariant = "Npgsql"; }) .ConfigureEndpoints(advertisedIP, siloPort, gatewayPort) .Configure(options => { options.ServiceId = "ManagingApp"; options.ClusterId = configuration["ASPNETCORE_ENVIRONMENT"] ?? "Development"; }) .Configure(options => { // Configure silo address for multi-server clustering options.SiloName = $"ManagingApi-{taskSlot}"; // Orleans will use the configured endpoints for clustering }); } else { // Fallback to localhost clustering for testing or when database is unavailable siloBuilder.UseLocalhostClustering(siloPort, gatewayPort); } // Conditionally configure reminder service based on flag if (runOrleansGrains && !disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString)) { siloBuilder.UseAdoNetReminderService(options => { options.ConnectionString = postgreSqlConnectionString; options.Invariant = "Npgsql"; }); } // Configure networking - use specific IP for Docker containerized environments if (disableOrleansClustering) { // Use localhost clustering when clustering is disabled siloBuilder.ConfigureEndpoints(IPAddress.Loopback, siloPort, gatewayPort); } else if (string.IsNullOrEmpty(postgreSqlConnectionString)) { // In Docker/containerized environments, use the resolved IP address siloBuilder.ConfigureEndpoints(advertisedIP, siloPort, gatewayPort); } siloBuilder .Configure(options => { // Configure messaging for better reliability with increased timeouts options.ResponseTimeout = TimeSpan.FromSeconds(60); options.DropExpiredMessages = true; }) .Configure(options => { // Configure cluster membership for better resilience options.EnableIndirectProbes = true; options.ProbeTimeout = TimeSpan.FromSeconds(10); options.IAmAliveTablePublishTimeout = TimeSpan.FromSeconds(30); options.MaxJoinAttemptTime = TimeSpan.FromSeconds(120); // Improved settings for development environments with stale members options.DefunctSiloCleanupPeriod = TimeSpan.FromMinutes(1); options.DefunctSiloExpiration = TimeSpan.FromMinutes(2); }) .Configure(options => { // Configure gateway with improved timeouts options.GatewayListRefreshPeriod = TimeSpan.FromSeconds(60); }); // Conditionally configure grain execution based on flag if (runOrleansGrains) { siloBuilder.Configure(options => { // Enable grain collection for active grains options.CollectionAge = TimeSpan.FromMinutes(10); options.CollectionQuantum = TimeSpan.FromMinutes(1); }); } else { // Disable grain execution completely siloBuilder.Configure(options => { // Disable grain collection to prevent grains from running options.CollectionAge = TimeSpan.FromDays(365); // Very long collection age options.CollectionQuantum = TimeSpan.FromDays(1); // Very long quantum }); } siloBuilder .ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Information)); // Only enable dashboard in development to avoid shutdown issues if (!isProduction) { siloBuilder.UseDashboard(options => { // Configure dashboard with proper shutdown handling and unique ports per instance options.Port = dashboardPort; options.HostSelf = true; options.CounterUpdateIntervalMs = 10000; // 10 seconds options.HideTrace = true; // Hide trace to reduce dashboard overhead options.Host = "0.0.0.0"; // Allow external connections options.Username = "admin"; options.Password = "admin"; }); } // Configure grain storage - use ADO.NET for production or memory for fallback if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString)) { siloBuilder .AddAdoNetGrainStorage("bot-store", options => { options.ConnectionString = postgreSqlConnectionString; options.Invariant = "Npgsql"; }) .AddAdoNetGrainStorage("registry-store", options => { options.ConnectionString = postgreSqlConnectionString; options.Invariant = "Npgsql"; }) .AddAdoNetGrainStorage("agent-store", options => { options.ConnectionString = postgreSqlConnectionString; options.Invariant = "Npgsql"; }) .AddAdoNetGrainStorage("platform-summary-store", options => { options.ConnectionString = postgreSqlConnectionString; options.Invariant = "Npgsql"; }); } else { // Fallback to memory storage when database is unavailable siloBuilder .AddMemoryGrainStorage("bot-store") .AddMemoryGrainStorage("registry-store") .AddMemoryGrainStorage("agent-store") .AddMemoryGrainStorage("platform-summary-store"); } siloBuilder .ConfigureServices(services => { // Register existing services for Orleans DI // These will be available to grains through dependency injection services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); }); }) ; } private static IServiceCollection AddApplication(this IServiceCollection services) { services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddTransient, OpenPositionCommandHandler>(); services.AddTransient, ClosePositionCommandHandler>(); // Processors services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddSingleton(); services.AddSingleton(); // Admin services services.AddSingleton(); return services; } private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { // Database services.AddSingleton(sp => sp.GetRequiredService>().Value); services.AddSingleton(sp => sp.GetRequiredService>().Value); services.AddSingleton(sp => sp.GetRequiredService>().Value); services.Configure(configuration.GetSection("Kaigen")); // Evm services.AddGbcFeed(); services.AddUniswapV2(); services.AddChainlink(); services.AddChainlinkGmx(); services.AddSingleton(); // PostgreSql Repositories services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // InfluxDb Repositories services.AddTransient(); services.AddTransient(); services.AddTransient(); // Cache services.AddDistributedMemoryCache(); services.AddTransient(); services.AddSingleton(); // Services services.AddTransient(); services.AddSingleton(); return services; } private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration) { // Balance Workers if (configuration.GetValue("WorkerBalancesTracking", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerNotifyBundleBacktest", false)) { services.AddHostedService(); } // Price Workers if (configuration.GetValue("WorkerPricesFifteenMinutes", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerPricesOneHour", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerPricesFourHours", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerPricesOneDay", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerPricesFiveMinutes", false)) { services.AddHostedService(); } // Other Workers if (configuration.GetValue("WorkerSpotlight", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerTraderWatcher", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerLeaderboard", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerFundingRatesWatcher", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerGeneticAlgorithm", false)) { services.AddHostedService(); } if (configuration.GetValue("WorkerBundleBacktest", false)) { services.AddHostedService(); } return services; } private static IServiceCollection AddMediatR(this IServiceCollection services) { return services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies())) .AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)) .AddTransient(typeof(IUnhandledExceptionBehaviour<,>), typeof(UnhandledExceptionBehaviour<,>)); } private static IServiceCollection AddFluentValidation(this IServiceCollection services) { return services.AddValidatorsFromAssembly(ApplicationAssembly); } public static IWebHostBuilder SetupDiscordBot(this IWebHostBuilder builder) { return builder.ConfigureServices(services => { services .AddSingleton() .AddSingleton() .AddSingleton() .AddHostedService(); }); } }