Compare commits
11 Commits
9841219e8b
...
955c358138
| Author | SHA1 | Date | |
|---|---|---|---|
| 955c358138 | |||
| 750f6cebbb | |||
| 14f5cb0971 | |||
| 7271889bdf | |||
| 3dbd2e91ea | |||
| 4ff2ccdae3 | |||
| 7923b38a26 | |||
| 2861a7f469 | |||
| 6df6061d66 | |||
| eeb2923646 | |||
| d2975be0f5 |
4
.github/workflows/dotnet.yml
vendored
4
.github/workflows/dotnet.yml
vendored
@@ -24,7 +24,3 @@ jobs:
|
|||||||
run: dotnet restore ./src/Managing.Api/Managing.Api.csproj
|
run: dotnet restore ./src/Managing.Api/Managing.Api.csproj
|
||||||
- name: Build API
|
- name: Build API
|
||||||
run: dotnet build --no-restore ./src/Managing.Api/Managing.Api.csproj
|
run: dotnet build --no-restore ./src/Managing.Api/Managing.Api.csproj
|
||||||
- name: Restore Worker dependencies
|
|
||||||
run: dotnet restore ./src/Managing.Api.Workers/Managing.Api.Workers.csproj
|
|
||||||
- name: Build Worker
|
|
||||||
run: dotnet build --no-restore ./src/Managing.Api.Workers/Managing.Api.Workers.csproj
|
|
||||||
|
|||||||
114
WORKER_CONSOLIDATION_SUMMARY.md
Normal file
114
WORKER_CONSOLIDATION_SUMMARY.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# Worker Consolidation Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Successfully consolidated the separate Managing.Api.Workers project into the main Managing.Api project as background services. This eliminates Orleans conflicts and simplifies deployment while maintaining all worker functionality.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### 1. ✅ Updated ApiBootstrap.cs
|
||||||
|
- **File**: `src/Managing.Bootstrap/ApiBootstrap.cs`
|
||||||
|
- **Changes**: Added all worker services from WorkersBootstrap to the main AddWorkers method
|
||||||
|
- **Workers Added**:
|
||||||
|
- PricesFifteenMinutesWorker
|
||||||
|
- PricesOneHourWorker
|
||||||
|
- PricesFourHoursWorker
|
||||||
|
- PricesOneDayWorker
|
||||||
|
- PricesFiveMinutesWorker
|
||||||
|
- SpotlightWorker
|
||||||
|
- TraderWatcher
|
||||||
|
- LeaderboardWorker
|
||||||
|
- FundingRatesWatcher
|
||||||
|
- GeneticAlgorithmWorker
|
||||||
|
- BundleBacktestWorker
|
||||||
|
- BalanceTrackingWorker
|
||||||
|
- NotifyBundleBacktestWorker
|
||||||
|
|
||||||
|
### 2. ✅ Configuration Files Updated
|
||||||
|
- **File**: `src/Managing.Api/appsettings.json`
|
||||||
|
- **File**: `src/Managing.Api/appsettings.Oda-docker.json`
|
||||||
|
- **Changes**: Added worker configuration flags to control which workers run
|
||||||
|
- **Default Values**: All workers disabled by default (set to `false`)
|
||||||
|
|
||||||
|
### 3. ✅ Deployment Scripts Updated
|
||||||
|
- **Files**:
|
||||||
|
- `scripts/build_and_run.sh`
|
||||||
|
- `scripts/docker-deploy-local.cmd`
|
||||||
|
- `scripts/docker-redeploy-oda.cmd`
|
||||||
|
- `scripts/docker-deploy-sandbox.cmd`
|
||||||
|
- **Changes**: Removed worker-specific build and deployment commands
|
||||||
|
|
||||||
|
### 4. ✅ Docker Compose Files Updated
|
||||||
|
- **Files**:
|
||||||
|
- `src/Managing.Docker/docker-compose.yml`
|
||||||
|
- `src/Managing.Docker/docker-compose.local.yml`
|
||||||
|
- **Changes**: Removed managing.api.workers service definitions
|
||||||
|
|
||||||
|
### 5. ✅ Workers Project Deprecated
|
||||||
|
- **File**: `src/Managing.Api.Workers/Program.cs`
|
||||||
|
- **Changes**: Added deprecation notice and removed Orleans configuration
|
||||||
|
- **Note**: Project kept for reference but should not be deployed
|
||||||
|
|
||||||
|
## Benefits Achieved
|
||||||
|
|
||||||
|
### ✅ Orleans Conflicts Resolved
|
||||||
|
- **Before**: Two Orleans clusters competing for same ports (11111/30000)
|
||||||
|
- **After**: Single Orleans cluster in main API
|
||||||
|
- **Impact**: No more port conflicts or cluster identity conflicts
|
||||||
|
|
||||||
|
### ✅ Simplified Architecture
|
||||||
|
- **Before**: Two separate applications to deploy and monitor
|
||||||
|
- **After**: Single application with all functionality
|
||||||
|
- **Impact**: Easier deployment, monitoring, and debugging
|
||||||
|
|
||||||
|
### ✅ Resource Efficiency
|
||||||
|
- **Before**: Duplicate service registrations and database connections
|
||||||
|
- **After**: Shared resources and connection pools
|
||||||
|
- **Impact**: Better performance and resource utilization
|
||||||
|
|
||||||
|
### ✅ Configuration Management
|
||||||
|
- **Before**: Separate configuration files for workers
|
||||||
|
- **After**: Centralized configuration with worker flags
|
||||||
|
- **Impact**: Easier to manage and control worker execution
|
||||||
|
|
||||||
|
## How to Enable/Disable Workers
|
||||||
|
|
||||||
|
Workers are controlled via configuration flags in `appsettings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"WorkerPricesFifteenMinutes": false,
|
||||||
|
"WorkerPricesOneHour": false,
|
||||||
|
"WorkerPricesFourHours": false,
|
||||||
|
"WorkerPricesOneDay": false,
|
||||||
|
"WorkerPricesFiveMinutes": false,
|
||||||
|
"WorkerSpotlight": false,
|
||||||
|
"WorkerTraderWatcher": false,
|
||||||
|
"WorkerLeaderboard": false,
|
||||||
|
"WorkerFundingRatesWatcher": false,
|
||||||
|
"WorkerGeneticAlgorithm": false,
|
||||||
|
"WorkerBundleBacktest": false,
|
||||||
|
"WorkerBalancesTracking": false,
|
||||||
|
"WorkerNotifyBundleBacktest": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set any worker to `true` to enable it in that environment.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### ✅ Build Verification
|
||||||
|
- Main API project builds successfully
|
||||||
|
- All worker dependencies resolved
|
||||||
|
- No compilation errors
|
||||||
|
|
||||||
|
### Next Steps for Full Verification
|
||||||
|
1. **Runtime Testing**: Start the main API and verify workers load correctly
|
||||||
|
2. **Worker Functionality**: Test that enabled workers execute as expected
|
||||||
|
3. **Orleans Integration**: Verify workers can access Orleans grains properly
|
||||||
|
4. **Configuration Testing**: Test enabling/disabling workers via config
|
||||||
|
|
||||||
|
## Migration Complete
|
||||||
|
|
||||||
|
The worker consolidation is now complete. The Managing.Api project now contains all functionality previously split between the API and Workers projects, providing a more maintainable and efficient architecture.
|
||||||
|
|
||||||
|
**Deployment**: Use only the main API deployment scripts. The Workers project should not be deployed.
|
||||||
@@ -3,11 +3,8 @@
|
|||||||
# Navigate to the src directory
|
# Navigate to the src directory
|
||||||
cd ../src
|
cd ../src
|
||||||
|
|
||||||
# Build the managing.api image
|
# Build the managing.api image (now includes all workers as background services)
|
||||||
docker build -t managing.api -f Managing.Api/Dockerfile . --no-cache
|
docker build -t managing.api -f Managing.Api/Dockerfile . --no-cache
|
||||||
|
|
||||||
# Build the managing.api.workers image
|
|
||||||
docker build -t managing.api.workers -f Managing.Api.Workers/Dockerfile . --no-cache
|
|
||||||
|
|
||||||
# Start up the project using docker-compose
|
# Start up the project using docker-compose
|
||||||
docker compose -f Managing.Docker/docker-compose.yml -f Managing.Docker/docker-compose.local.yml up -d
|
docker compose -f Managing.Docker/docker-compose.yml -f Managing.Docker/docker-compose.local.yml up -d
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
cd ..
|
cd ..
|
||||||
cd .\src\
|
cd .\src\
|
||||||
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
||||||
docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache
|
|
||||||
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.local.yml up -d
|
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.local.yml up -d
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
cd ..
|
cd ..
|
||||||
cd .\src\
|
cd .\src\
|
||||||
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
||||||
docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache
|
|
||||||
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d
|
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d
|
||||||
@@ -2,21 +2,16 @@ cd ..
|
|||||||
cd .\src\
|
cd .\src\
|
||||||
ECHO "Stopping containers..."
|
ECHO "Stopping containers..."
|
||||||
docker stop sandbox-managing.api-1
|
docker stop sandbox-managing.api-1
|
||||||
docker stop sandbox-managing.api.workers-1
|
|
||||||
ECHO "Contaiters stopped"
|
ECHO "Contaiters stopped"
|
||||||
ECHO "Removing containers..."
|
ECHO "Removing containers..."
|
||||||
docker rm sandbox-managing.api-1
|
docker rm sandbox-managing.api-1
|
||||||
docker rm sandbox-managing.api.workers-1
|
|
||||||
ECHO "Containers removed"
|
ECHO "Containers removed"
|
||||||
ECHO "Removing images..."
|
ECHO "Removing images..."
|
||||||
docker rmi managing.api
|
docker rmi managing.api
|
||||||
docker rmi managing.api:latest
|
docker rmi managing.api:latest
|
||||||
docker rmi managing.api.workers
|
|
||||||
docker rmi managing.api.workers:latest
|
|
||||||
ECHO "Images removed"
|
ECHO "Images removed"
|
||||||
ECHO "Building images..."
|
ECHO "Building images..."
|
||||||
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
docker build -t managing.api -f ./Managing.Api/Dockerfile . --no-cache
|
||||||
docker build -t managing.api.workers -f ./Managing.Api.Workers/Dockerfile . --no-cache
|
|
||||||
ECHO "Deploying..."
|
ECHO "Deploying..."
|
||||||
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d
|
docker-compose -f ./Managing.Docker/docker-compose.yml -f ./Managing.Docker/docker-compose.sandbox.yml up -d
|
||||||
ECHO "Deployed"
|
ECHO "Deployed"
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
|
||||||
using Managing.Domain.Workers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using static Managing.Common.Enums;
|
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Controllers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
[Produces("application/json")]
|
|
||||||
public class WorkerController : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly IWorkerService _workerService;
|
|
||||||
|
|
||||||
public WorkerController(IWorkerService workerService)
|
|
||||||
{
|
|
||||||
_workerService = workerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<ActionResult<List<Worker>>> GetWorkers()
|
|
||||||
{
|
|
||||||
var workers = await _workerService.GetWorkers();
|
|
||||||
return Ok(workers.ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch]
|
|
||||||
public async Task<ActionResult> ToggleWorker(WorkerType workerType)
|
|
||||||
{
|
|
||||||
return Ok(await _workerService.ToggleWorker(workerType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Use the official Microsoft ASP.NET Core runtime as the base image.
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
|
||||||
WORKDIR /app
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 443
|
|
||||||
|
|
||||||
# Use the official Microsoft .NET SDK image to build the code.
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
|
||||||
WORKDIR /src
|
|
||||||
COPY ["Managing.Api.Workers/Managing.Api.Workers.csproj", "Managing.Api.Workers/"]
|
|
||||||
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
|
|
||||||
COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
|
||||||
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
|
|
||||||
COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"]
|
|
||||||
COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"]
|
|
||||||
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
|
||||||
COPY ["Managing.Domain/Managing.Domain.csproj", "Managing.Domain/"]
|
|
||||||
COPY ["Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"]
|
|
||||||
COPY ["Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj", "Managing.Infrastructure.Exchanges/"]
|
|
||||||
COPY ["Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj", "Managing.Infrastructure.Database/"]
|
|
||||||
RUN dotnet restore "Managing.Api.Workers/Managing.Api.Workers.csproj"
|
|
||||||
COPY . .
|
|
||||||
WORKDIR "/src/Managing.Api.Workers"
|
|
||||||
RUN dotnet build "Managing.Api.Workers.csproj" -c Release -o /app/build
|
|
||||||
|
|
||||||
FROM build AS publish
|
|
||||||
RUN dotnet publish "Managing.Api.Workers.csproj" -c Release -o /app/publish
|
|
||||||
|
|
||||||
FROM base AS final
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=publish /app/publish .
|
|
||||||
COPY managing_cert.pfx .
|
|
||||||
ENTRYPOINT ["dotnet", "Managing.Api.Workers.dll"]
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Microsoft.OpenApi.Any;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Filters
|
|
||||||
{
|
|
||||||
public class EnumSchemaFilter : ISchemaFilter
|
|
||||||
{
|
|
||||||
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
|
||||||
{
|
|
||||||
if (context.Type.IsEnum)
|
|
||||||
{
|
|
||||||
model.Enum.Clear();
|
|
||||||
Enum.GetNames(context.Type)
|
|
||||||
.ToList()
|
|
||||||
.ForEach(n => model.Enum.Add(new OpenApiString(n)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
|
||||||
<UserSecretsId>3900ce93-de15-49e5-9a61-7dc2209939ca</UserSecretsId>
|
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
|
||||||
<DockerfileContext>..\..</DockerfileContext>
|
|
||||||
<DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="8.1.0"/>
|
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0"/>
|
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="9.0.0"/>
|
|
||||||
<PackageReference Include="Essential.LoggerProvider.Elasticsearch" Version="1.3.2"/>
|
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1"/>
|
|
||||||
<PackageReference Include="NSwag.AspNetCore" Version="14.0.7"/>
|
|
||||||
<PackageReference Include="Sentry.AspNetCore" Version="5.5.1"/>
|
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
|
|
||||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0"/>
|
|
||||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0"/>
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
|
|
||||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0"/>
|
|
||||||
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0"/>
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.6.1"/>
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.6.1"/>
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.1"/>
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.6.1"/>
|
|
||||||
<PackageReference Include="xunit" Version="2.8.0"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj"/>
|
|
||||||
<ProjectReference Include="..\Managing.Aspire.ServiceDefaults\Managing.Aspire.ServiceDefaults.csproj"/>
|
|
||||||
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Update="appsettings.Oda.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Update="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Update="appsettings.Sandbox.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Update="appsettings.SandboxLocal.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Update="appsettings.Production.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Update="appsettings.KaiServer.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
using Sentry;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Managing.Api.Workers.Middleware
|
|
||||||
{
|
|
||||||
public class SentryDiagnosticsMiddleware
|
|
||||||
{
|
|
||||||
private readonly RequestDelegate _next;
|
|
||||||
private readonly ILogger<SentryDiagnosticsMiddleware> _logger;
|
|
||||||
|
|
||||||
public SentryDiagnosticsMiddleware(RequestDelegate next, ILogger<SentryDiagnosticsMiddleware> logger)
|
|
||||||
{
|
|
||||||
_next = next;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext context)
|
|
||||||
{
|
|
||||||
// Only activate for the /api/sentry-diagnostics endpoint
|
|
||||||
if (context.Request.Path.StartsWithSegments("/api/sentry-diagnostics"))
|
|
||||||
{
|
|
||||||
await HandleDiagnosticsRequest(context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _next(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleDiagnosticsRequest(HttpContext context)
|
|
||||||
{
|
|
||||||
var response = new StringBuilder();
|
|
||||||
response.AppendLine("Sentry Diagnostics Report");
|
|
||||||
response.AppendLine("========================");
|
|
||||||
response.AppendLine($"Timestamp: {DateTime.Now}");
|
|
||||||
response.AppendLine();
|
|
||||||
|
|
||||||
// Check if Sentry is initialized
|
|
||||||
response.AppendLine("## Sentry SDK Status");
|
|
||||||
response.AppendLine($"Sentry Enabled: {SentrySdk.IsEnabled}");
|
|
||||||
response.AppendLine($"Application Environment: {Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}");
|
|
||||||
response.AppendLine();
|
|
||||||
|
|
||||||
// Send a test event
|
|
||||||
response.AppendLine("## Test Event");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var id = SentrySdk.CaptureMessage($"Diagnostics test from {context.Request.Host} at {DateTime.Now}", SentryLevel.Info);
|
|
||||||
response.AppendLine($"Test Event ID: {id}");
|
|
||||||
response.AppendLine("Test event was sent to Sentry. Check your Sentry dashboard to confirm it was received.");
|
|
||||||
|
|
||||||
// Try to send an exception too
|
|
||||||
try
|
|
||||||
{
|
|
||||||
throw new Exception("Test exception from diagnostics middleware");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var exceptionId = SentrySdk.CaptureException(ex);
|
|
||||||
response.AppendLine($"Test Exception ID: {exceptionId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
response.AppendLine($"Error sending test event: {ex.Message}");
|
|
||||||
response.AppendLine(ex.StackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.AppendLine();
|
|
||||||
response.AppendLine("## Connectivity Check");
|
|
||||||
response.AppendLine("If events are not appearing in Sentry, check the following:");
|
|
||||||
response.AppendLine("1. Verify your DSN is correct in appsettings.json");
|
|
||||||
response.AppendLine("2. Ensure your network allows outbound HTTPS connections to sentry.apps.managing.live");
|
|
||||||
response.AppendLine("3. Check Sentry server logs for any ingestion issues");
|
|
||||||
response.AppendLine("4. Verify your Sentry project is correctly configured to receive events");
|
|
||||||
|
|
||||||
// Return the diagnostic information
|
|
||||||
context.Response.ContentType = "text/plain";
|
|
||||||
await context.Response.WriteAsync(response.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extension method used to add the middleware to the HTTP request pipeline.
|
|
||||||
public static class SentryDiagnosticsMiddlewareExtensions
|
|
||||||
{
|
|
||||||
public static IApplicationBuilder UseSentryDiagnostics(this IApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
return builder.UseMiddleware<SentryDiagnosticsMiddleware>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
using HealthChecks.UI.Client;
|
|
||||||
using Managing.Api.Workers.Filters;
|
|
||||||
using Managing.Application.Hubs;
|
|
||||||
using Managing.Bootstrap;
|
|
||||||
using Managing.Common;
|
|
||||||
using Managing.Core.Middleawares;
|
|
||||||
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
|
||||||
using Managing.Infrastructure.Databases.PostgreSql;
|
|
||||||
using Managing.Infrastructure.Evm.Models.Privy;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using NSwag;
|
|
||||||
using NSwag.Generation.Processors.Security;
|
|
||||||
using Serilog;
|
|
||||||
using Serilog.Events;
|
|
||||||
using Serilog.Sinks.Elasticsearch;
|
|
||||||
using OpenApiSecurityRequirement = Microsoft.OpenApi.Models.OpenApiSecurityRequirement;
|
|
||||||
using OpenApiSecurityScheme = NSwag.OpenApiSecurityScheme;
|
|
||||||
|
|
||||||
// Builder
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
builder.Configuration.SetBasePath(AppContext.BaseDirectory);
|
|
||||||
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
|
||||||
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json");
|
|
||||||
|
|
||||||
var influxUrl = builder.Configuration.GetSection(Constants.Databases.InfluxDb)["Url"];
|
|
||||||
var web3ProxyUrl = builder.Configuration.GetSection("Web3Proxy")["BaseUrl"];
|
|
||||||
var postgreSqlConnectionString = builder.Configuration.GetSection("PostgreSql")["ConnectionString"];
|
|
||||||
|
|
||||||
// Initialize Sentry
|
|
||||||
SentrySdk.Init(options =>
|
|
||||||
{
|
|
||||||
// A Sentry Data Source Name (DSN) is required.
|
|
||||||
options.Dsn = builder.Configuration["Sentry:Dsn"];
|
|
||||||
|
|
||||||
// When debug is enabled, the Sentry client will emit detailed debugging information to the console.
|
|
||||||
options.Debug = false;
|
|
||||||
|
|
||||||
// Adds request URL and headers, IP and name for users, etc.
|
|
||||||
options.SendDefaultPii = true;
|
|
||||||
|
|
||||||
// This option is recommended. It enables Sentry's "Release Health" feature.
|
|
||||||
options.AutoSessionTracking = true;
|
|
||||||
|
|
||||||
// Enabling this option is recommended for client applications only. It ensures all threads use the same global scope.
|
|
||||||
options.IsGlobalModeEnabled = false;
|
|
||||||
|
|
||||||
// Example sample rate for your transactions: captures 10% of transactions
|
|
||||||
options.TracesSampleRate = 0.1;
|
|
||||||
|
|
||||||
options.Environment = builder.Environment.EnvironmentName;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add service discovery for Aspire
|
|
||||||
builder.Services.AddServiceDiscovery();
|
|
||||||
|
|
||||||
// Configure health checks
|
|
||||||
builder.Services.AddHealthChecks()
|
|
||||||
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
|
|
||||||
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
|
|
||||||
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
|
|
||||||
|
|
||||||
builder.WebHost.UseUrls("http://localhost:5001");
|
|
||||||
|
|
||||||
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
|
|
||||||
{
|
|
||||||
var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
|
|
||||||
var indexFormat = $"managing-worker-{envName}-" + "{0:yyyy.MM.dd}";
|
|
||||||
var yourTemplateName = "dotnetlogs";
|
|
||||||
var es = new ElasticsearchSinkOptions(new Uri(hostBuilder.Configuration["ElasticConfiguration:Uri"]))
|
|
||||||
{
|
|
||||||
IndexFormat = indexFormat.ToLower(),
|
|
||||||
AutoRegisterTemplate = true,
|
|
||||||
OverwriteTemplate = true,
|
|
||||||
TemplateName = yourTemplateName,
|
|
||||||
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
|
|
||||||
TypeName = null,
|
|
||||||
BatchAction = ElasticOpType.Create,
|
|
||||||
MinimumLogEventLevel = LogEventLevel.Information,
|
|
||||||
DetectElasticsearchVersion = true,
|
|
||||||
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
|
|
||||||
};
|
|
||||||
|
|
||||||
loggerConfiguration
|
|
||||||
.WriteTo.Console()
|
|
||||||
.WriteTo.Elasticsearch(es);
|
|
||||||
});
|
|
||||||
builder.Services.AddOptions();
|
|
||||||
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
|
|
||||||
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
|
|
||||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
|
||||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
|
||||||
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
|
|
||||||
{
|
|
||||||
builder
|
|
||||||
.SetIsOriginAllowed((host) => true)
|
|
||||||
.AllowAnyOrigin()
|
|
||||||
.WithOrigins("http://localhost:3000/")
|
|
||||||
.AllowAnyMethod()
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowCredentials();
|
|
||||||
}));
|
|
||||||
builder.Services.AddSignalR().AddJsonProtocol();
|
|
||||||
|
|
||||||
// Add PostgreSQL DbContext for worker services
|
|
||||||
builder.Services.AddDbContext<ManagingDbContext>(options =>
|
|
||||||
{
|
|
||||||
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
|
|
||||||
{
|
|
||||||
npgsqlOptions.CommandTimeout(60);
|
|
||||||
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10),
|
|
||||||
errorCodesToAdd: null);
|
|
||||||
});
|
|
||||||
if (builder.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
options.EnableDetailedErrors();
|
|
||||||
options.EnableSensitiveDataLogging();
|
|
||||||
options.EnableThreadSafetyChecks();
|
|
||||||
}
|
|
||||||
|
|
||||||
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
|
||||||
options.EnableServiceProviderCaching();
|
|
||||||
options.LogTo(msg => Console.WriteLine(msg), LogLevel.Warning);
|
|
||||||
}, ServiceLifetime.Scoped);
|
|
||||||
|
|
||||||
builder.Services.RegisterWorkersDependencies(builder.Configuration);
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddOpenApiDocument(document =>
|
|
||||||
{
|
|
||||||
document.AddSecurity("JWT", Enumerable.Empty<string>(), new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Type = OpenApiSecuritySchemeType.ApiKey,
|
|
||||||
Name = "Authorization",
|
|
||||||
In = OpenApiSecurityApiKeyLocation.Header,
|
|
||||||
Description = "Type into the textbox: Bearer {your JWT token}."
|
|
||||||
});
|
|
||||||
|
|
||||||
document.OperationProcessors.Add(
|
|
||||||
new AspNetCoreOperationSecurityScopeProcessor("JWT"));
|
|
||||||
});
|
|
||||||
builder.Services.AddSwaggerGen(options =>
|
|
||||||
{
|
|
||||||
options.SchemaFilter<EnumSchemaFilter>();
|
|
||||||
options.AddSecurityDefinition("Bearer,", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Description = "Please insert your JWT Token into field : Bearer {your_token}",
|
|
||||||
Name = "Authorization",
|
|
||||||
Type = SecuritySchemeType.Http,
|
|
||||||
In = ParameterLocation.Header,
|
|
||||||
Scheme = "Bearer",
|
|
||||||
BearerFormat = "JWT"
|
|
||||||
});
|
|
||||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
|
||||||
{
|
|
||||||
{
|
|
||||||
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Reference = new OpenApiReference
|
|
||||||
{
|
|
||||||
Type = ReferenceType.SecurityScheme,
|
|
||||||
Id = "Bearer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new string[] { }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.WebHost.SetupDiscordBot();
|
|
||||||
|
|
||||||
// App
|
|
||||||
var app = builder.Build();
|
|
||||||
app.UseSerilogRequestLogging();
|
|
||||||
|
|
||||||
app.UseOpenApi();
|
|
||||||
app.UseSwaggerUI(c =>
|
|
||||||
{
|
|
||||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Managing Workers v1");
|
|
||||||
c.RoutePrefix = string.Empty;
|
|
||||||
});
|
|
||||||
|
|
||||||
app.UseCors("CorsPolicy");
|
|
||||||
|
|
||||||
// Add Sentry diagnostics middleware (now using shared version from Core)
|
|
||||||
app.UseSentryDiagnostics();
|
|
||||||
|
|
||||||
// Using shared GlobalErrorHandlingMiddleware from Core project
|
|
||||||
app.UseMiddleware<GlobalErrorHandlingMiddleware>();
|
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
|
||||||
|
|
||||||
app.UseRouting();
|
|
||||||
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
|
||||||
{
|
|
||||||
endpoints.MapControllers();
|
|
||||||
endpoints.MapHub<PositionHub>("/positionhub");
|
|
||||||
|
|
||||||
endpoints.MapHealthChecks("/health", new HealthCheckOptions
|
|
||||||
{
|
|
||||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
||||||
});
|
|
||||||
|
|
||||||
endpoints.MapHealthChecks("/alive", new HealthCheckOptions
|
|
||||||
{
|
|
||||||
Predicate = r => r.Tags.Contains("live"),
|
|
||||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.Run();
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "http://localhost:8086/",
|
|
||||||
"Token": ""
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://localhost:9200"
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "http://influxdb:8086/",
|
|
||||||
"Organization": "managing-org",
|
|
||||||
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
|
||||||
},
|
|
||||||
"Privy": {
|
|
||||||
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
|
||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://elasticsearch:9200"
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"PostgreSql": {
|
|
||||||
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres",
|
|
||||||
"Orleans": "Host=localhost;Port=5432;Database=orleans;Username=postgres;Password=postgres"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "http://localhost:8086/",
|
|
||||||
"Organization": "managing-org",
|
|
||||||
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
|
|
||||||
},
|
|
||||||
"Privy": {
|
|
||||||
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
|
||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://localhost:9200"
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*",
|
|
||||||
"WorkerPricesFifteenMinutes": false,
|
|
||||||
"WorkerPricesOneHour": false,
|
|
||||||
"WorkerPricesFourHours": false,
|
|
||||||
"WorkerPricesOneDay": false,
|
|
||||||
"WorkerPricesFiveMinutes": false,
|
|
||||||
"WorkerFee": false,
|
|
||||||
"WorkerPositionManager": false,
|
|
||||||
"WorkerPositionFetcher": false,
|
|
||||||
"WorkerSpotlight": false,
|
|
||||||
"WorkerTraderWatcher": false,
|
|
||||||
"WorkerLeaderboard": false,
|
|
||||||
"WorkerFundingRatesWatcher": false,
|
|
||||||
"WorkerGeneticAlgorithm": false,
|
|
||||||
"WorkerBundleBacktest": true
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"PostgreSql": {
|
|
||||||
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37",
|
|
||||||
"Orleans": "Host=managing-postgre.apps.managing.live;Port=5432;Database=orleans;Username=postgres;Password=29032b13a5bc4d37"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
|
||||||
"Organization": "managing-org",
|
|
||||||
"Token": "_BtklT_aQ7GRqWG-HGILYEd8MJzxdbxxckPadzUsRofnwJBKQuXYLbCrVcLD7TrD4BlXgGAsyuqQItsOtanfBw=="
|
|
||||||
},
|
|
||||||
"Privy": {
|
|
||||||
"AppId": "cm6kkz5ke00n5ffmpwdbr05mp",
|
|
||||||
"AppSecret": "3STq1UyPJ5WHixArBcVBKecWtyR4QpgZ1uju4HHvvJH2RwtacJnvoyzuaiNC8Xibi4rQb3eeH2YtncKrMxCYiV3a"
|
|
||||||
},
|
|
||||||
"Web3Proxy": {
|
|
||||||
"BaseUrl": "http://srv-captain--managing-web3:4111"
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://elasticsearch:9200"
|
|
||||||
},
|
|
||||||
"Sentry": {
|
|
||||||
"Dsn": "https://698e00d7cb404b049aff3881e5a47f6b@bugcenter.apps.managing.live/1"
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"PostgreSql": {
|
|
||||||
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "http://srv-captain--influx-db:8086/",
|
|
||||||
"Organization": "managing-org",
|
|
||||||
"Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ=="
|
|
||||||
},
|
|
||||||
"Privy": {
|
|
||||||
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
|
||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://elasticsearch:9200"
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
|
||||||
"Organization": "managing-org",
|
|
||||||
"Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ=="
|
|
||||||
},
|
|
||||||
"Privy": {
|
|
||||||
"AppId": "cm6f47n1l003jx7mjwaembhup",
|
|
||||||
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://elasticsearch:9200"
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "http://influxdb:8086/",
|
|
||||||
"Organization": "",
|
|
||||||
"Token": ""
|
|
||||||
},
|
|
||||||
"PostgreSql": {
|
|
||||||
"ConnectionString": "Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres"
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Information",
|
|
||||||
"System": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"N8n": {
|
|
||||||
"WebhookUrl": "https://n8n.kai.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951"
|
|
||||||
},
|
|
||||||
"ElasticConfiguration": {
|
|
||||||
"Uri": "http://elasticsearch:9200"
|
|
||||||
},
|
|
||||||
"Web3Proxy": {
|
|
||||||
"BaseUrl": "http://localhost:4111"
|
|
||||||
},
|
|
||||||
"Sentry": {
|
|
||||||
"Dsn": "https://8fdb299b69df4f9d9b709c8d4a556608@bugcenter.apps.managing.live/2",
|
|
||||||
"MinimumEventLevel": "Error",
|
|
||||||
"SendDefaultPii": true,
|
|
||||||
"MaxBreadcrumbs": 50,
|
|
||||||
"SampleRate": 1.0,
|
|
||||||
"TracesSampleRate": 0.2,
|
|
||||||
"Debug": false
|
|
||||||
},
|
|
||||||
"Discord": {
|
|
||||||
"BotActivity": "with jobs",
|
|
||||||
"HandleUserAction": true,
|
|
||||||
"ApplicationId": "1132062339592622221",
|
|
||||||
"PublicKey": "e422f3326307788608eceba919497d3f2758cc64d20bb8a6504c695192404808",
|
|
||||||
"TokenId": "MTEzMjA2MjMzOTU5MjYyMjIyMQ.GySuNX.rU-9uIX6-yDthBjT_sbXioaJGyJva2ABNNEaj4",
|
|
||||||
"SignalChannelId": 966080506473099314,
|
|
||||||
"TradesChannelId": 998374177763491851,
|
|
||||||
"TroublesChannelId": 1015761955321040917,
|
|
||||||
"CopyTradingChannelId": 1132022887012909126,
|
|
||||||
"RequestsChannelId": 1018589494968078356,
|
|
||||||
"FundingRateChannelId": 1263566138709774336,
|
|
||||||
"LeaderboardChannelId": 1133169725237633095,
|
|
||||||
"ButtonExpirationMinutes": 10
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
80
src/Managing.Api/ADMIN_FEATURE.md
Normal file
80
src/Managing.Api/ADMIN_FEATURE.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Admin Feature Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The admin feature allows specific users to manage all bots in the system, regardless of ownership. Admin users can start, stop, delete, and modify any bot without owning the associated account.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
Admin privileges are granted through environment variables, making it secure and environment-specific. The system checks if a user is an admin by comparing their username against a comma-separated list of admin usernames configured in the environment.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variable
|
||||||
|
Set the `AdminUsers` environment variable with a comma-separated list of usernames:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
AdminUsers=admin1,superuser,john.doe
|
||||||
|
```
|
||||||
|
|
||||||
|
### CapRover Configuration
|
||||||
|
In your CapRover dashboard:
|
||||||
|
1. Go to your app's settings
|
||||||
|
2. Navigate to "Environment Variables"
|
||||||
|
3. Add a new environment variable:
|
||||||
|
- Key: `AdminUsers`
|
||||||
|
- Value: `admin1,superuser,john.doe` (replace with actual admin usernames)
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
For local development, you can set this in your `appsettings.Development.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"AdminUsers": "admin1,superuser,john.doe"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Admin Capabilities
|
||||||
|
|
||||||
|
Admin users can perform all bot operations without ownership restrictions:
|
||||||
|
|
||||||
|
- **Start/Save Bot**: Create and start bots for any account
|
||||||
|
- **Stop Bot**: Stop any running bot
|
||||||
|
- **Delete Bot**: Delete any bot
|
||||||
|
- **Restart Bot**: Restart any bot
|
||||||
|
- **Open/Close Positions**: Manually open or close positions for any bot
|
||||||
|
- **Update Configuration**: Modify any bot's configuration
|
||||||
|
- **View Bot Configuration**: Access any bot's configuration details
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
1. **Environment-Based**: Admin users are configured via environment variables, not through the API
|
||||||
|
2. **No Privilege Escalation**: Regular users cannot grant themselves admin access
|
||||||
|
3. **Audit Logging**: All admin actions are logged with the admin user's context
|
||||||
|
4. **Case-Insensitive**: Username matching is case-insensitive for convenience
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
The admin feature is implemented using:
|
||||||
|
- `IAdminConfigurationService`: Checks if a user is an admin
|
||||||
|
- Updated `UserOwnsBotAccount` method: Returns true for admin users
|
||||||
|
- Dependency injection: Service is registered as a singleton
|
||||||
|
- Configuration reading: Reads from `AdminUsers` environment variable
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
1. **Set Admin Users**:
|
||||||
|
```bash
|
||||||
|
AdminUsers=alice,bob,charlie
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Admin Operations**:
|
||||||
|
- Alice, Bob, or Charlie can now manage any bot in the system
|
||||||
|
- They can use all existing bot endpoints without ownership restrictions
|
||||||
|
- All operations are logged with their username for audit purposes
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Admin not working**: Check if the username exactly matches the configuration (case-insensitive)
|
||||||
|
- **No admins configured**: Check the `AdminUsers` environment variable is set
|
||||||
|
- **Multiple environments**: Each environment (dev, staging, prod) should have its own admin configuration
|
||||||
@@ -4,6 +4,7 @@ using Managing.Application.Abstractions;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
|
using Managing.Application.Shared;
|
||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
@@ -40,6 +41,7 @@ public class BotController : BaseController
|
|||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private readonly IAdminConfigurationService _adminService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BotController"/> class.
|
/// Initializes a new instance of the <see cref="BotController"/> class.
|
||||||
@@ -56,7 +58,7 @@ public class BotController : BaseController
|
|||||||
public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext,
|
public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext,
|
||||||
IBacktester backtester, IBotService botService, IUserService userService,
|
IBacktester backtester, IBotService botService, IUserService userService,
|
||||||
IAccountService accountService, IMoneyManagementService moneyManagementService,
|
IAccountService accountService, IMoneyManagementService moneyManagementService,
|
||||||
IServiceScopeFactory scopeFactory) : base(userService)
|
IServiceScopeFactory scopeFactory, IAdminConfigurationService adminService) : base(userService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
@@ -66,6 +68,7 @@ public class BotController : BaseController
|
|||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
_scopeFactory = scopeFactory;
|
_scopeFactory = scopeFactory;
|
||||||
|
_adminService = adminService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -73,7 +76,7 @@ public class BotController : BaseController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identifier">The identifier of the bot to check</param>
|
/// <param name="identifier">The identifier of the bot to check</param>
|
||||||
/// <param name="accountName">Optional account name to check when creating a new bot</param>
|
/// <param name="accountName">Optional account name to check when creating a new bot</param>
|
||||||
/// <returns>True if the user owns the account, False otherwise</returns>
|
/// <returns>True if the user owns the account or is admin, False otherwise</returns>
|
||||||
private async Task<bool> UserOwnsBotAccount(Guid identifier, string accountName = null)
|
private async Task<bool> UserOwnsBotAccount(Guid identifier, string accountName = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -82,6 +85,9 @@ public class BotController : BaseController
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Admin users can access all bots
|
||||||
|
if (_adminService.IsUserAdmin(user.Name))
|
||||||
|
return true;
|
||||||
|
|
||||||
if (identifier != default)
|
if (identifier != default)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Application.Shared;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
@@ -26,6 +27,8 @@ public class TradingController : BaseController
|
|||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
private readonly ILogger<TradingController> _logger;
|
private readonly ILogger<TradingController> _logger;
|
||||||
|
private readonly IAdminConfigurationService _adminService;
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TradingController"/> class.
|
/// Initializes a new instance of the <see cref="TradingController"/> class.
|
||||||
@@ -35,13 +38,16 @@ public class TradingController : BaseController
|
|||||||
/// <param name="closeTradeCommandHandler">Command handler for closing trades.</param>
|
/// <param name="closeTradeCommandHandler">Command handler for closing trades.</param>
|
||||||
/// <param name="tradingService">Service for trading operations.</param>
|
/// <param name="tradingService">Service for trading operations.</param>
|
||||||
/// <param name="mediator">Mediator for handling commands and requests.</param>
|
/// <param name="mediator">Mediator for handling commands and requests.</param>
|
||||||
|
/// <param name="adminService">Service for checking admin privileges.</param>
|
||||||
|
/// <param name="accountService">Service for account operations.</param>
|
||||||
public TradingController(
|
public TradingController(
|
||||||
ILogger<TradingController> logger,
|
ILogger<TradingController> logger,
|
||||||
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
|
ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
|
||||||
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
|
ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
|
||||||
ITradingService tradingService,
|
ITradingService tradingService,
|
||||||
IMediator mediator, IMoneyManagementService moneyManagementService,
|
IMediator mediator, IMoneyManagementService moneyManagementService,
|
||||||
IUserService userService) : base(userService)
|
IUserService userService, IAdminConfigurationService adminService,
|
||||||
|
IAccountService accountService) : base(userService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_openTradeCommandHandler = openTradeCommandHandler;
|
_openTradeCommandHandler = openTradeCommandHandler;
|
||||||
@@ -49,6 +55,8 @@ public class TradingController : BaseController
|
|||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_moneyManagementService = moneyManagementService;
|
_moneyManagementService = moneyManagementService;
|
||||||
|
_adminService = adminService;
|
||||||
|
_accountService = accountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -149,6 +157,7 @@ public class TradingController : BaseController
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a Privy wallet address for the user.
|
/// Initializes a Privy wallet address for the user.
|
||||||
|
/// Only admins can initialize any address, regular users can only initialize their own addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="publicAddress">The public address of the Privy wallet to initialize.</param>
|
/// <param name="publicAddress">The public address of the Privy wallet to initialize.</param>
|
||||||
/// <returns>The initialization response containing success status and transaction hashes.</returns>
|
/// <returns>The initialization response containing success status and transaction hashes.</returns>
|
||||||
@@ -162,6 +171,18 @@ public class TradingController : BaseController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var user = await GetUser();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return Unauthorized("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has permission to initialize this address
|
||||||
|
if (!await CanUserInitializeAddress(user.Name, publicAddress))
|
||||||
|
{
|
||||||
|
return Forbid("You don't have permission to initialize this wallet address. You can only initialize your own wallet addresses.");
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _tradingService.InitPrivyWallet(publicAddress);
|
var result = await _tradingService.InitPrivyWallet(publicAddress);
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
@@ -175,4 +196,42 @@ public class TradingController : BaseController
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the user can initialize the given public address.
|
||||||
|
/// Admins can initialize any address, regular users can only initialize their own addresses.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userName">The username to check</param>
|
||||||
|
/// <param name="publicAddress">The public address to initialize</param>
|
||||||
|
/// <returns>True if the user can initialize the address, false otherwise</returns>
|
||||||
|
private async Task<bool> CanUserInitializeAddress(string userName, string publicAddress)
|
||||||
|
{
|
||||||
|
// Admin users can initialize any address
|
||||||
|
if (_adminService.IsUserAdmin(userName))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Admin user {UserName} initializing address {Address}", userName, publicAddress);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Regular users can only initialize their own addresses
|
||||||
|
// Check if the address belongs to one of the user's accounts
|
||||||
|
var account = await _accountService.GetAccountByKey(publicAddress, true, false);
|
||||||
|
|
||||||
|
if (account?.User?.Name == userName)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User {UserName} initializing their own address {Address}", userName, publicAddress);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("User {UserName} attempted to initialize address {Address} that doesn't belong to them", userName, publicAddress);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Unable to verify ownership of address {Address} for user {UserName}", publicAddress, userName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -53,5 +53,8 @@
|
|||||||
<Content Update="appsettings.Production.json">
|
<Content Update="appsettings.Production.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Update="appsettings.KaiServer.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
119
src/Managing.Api/README-ORLEANS-TROUBLESHOOTING.md
Normal file
119
src/Managing.Api/README-ORLEANS-TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Orleans Clustering Troubleshooting Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document provides troubleshooting steps for Orleans clustering issues, particularly the "Connection attempt to endpoint failed" errors that can occur in Docker deployments.
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### 1. Connection Timeout Errors
|
||||||
|
|
||||||
|
**Error**: `Connection attempt to endpoint S10.0.0.9:11111:114298801 timed out after 00:00:05`
|
||||||
|
|
||||||
|
**Cause**: Orleans silos are trying to connect to each other but the network configuration is preventing proper communication.
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
#### A. Environment Variables
|
||||||
|
You can disable Orleans clustering completely by setting:
|
||||||
|
```bash
|
||||||
|
DISABLE_ORLEANS_CLUSTERING=true
|
||||||
|
```
|
||||||
|
|
||||||
|
This will fall back to localhost clustering mode for testing.
|
||||||
|
|
||||||
|
#### B. Docker Network Configuration
|
||||||
|
Ensure the Docker compose file includes proper network configuration:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
managing.api:
|
||||||
|
ports:
|
||||||
|
- "11111:11111" # Orleans silo port
|
||||||
|
- "30000:30000" # Orleans gateway port
|
||||||
|
hostname: managing-api
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Database Connection Issues
|
||||||
|
If the Orleans database is unavailable, the system will automatically fall back to:
|
||||||
|
- Localhost clustering
|
||||||
|
- Memory-based grain storage
|
||||||
|
|
||||||
|
### 2. Configuration Options
|
||||||
|
|
||||||
|
#### Production Settings
|
||||||
|
In `appsettings.Production.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"RunOrleansGrains": true,
|
||||||
|
"Orleans": {
|
||||||
|
"EnableClustering": true,
|
||||||
|
"ConnectionTimeout": 60,
|
||||||
|
"MaxJoinAttempts": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment Variables
|
||||||
|
- `RUN_ORLEANS_GRAINS`: Enable/disable Orleans grains (true/false)
|
||||||
|
- `DISABLE_ORLEANS_CLUSTERING`: Force localhost clustering (true/false)
|
||||||
|
- `ORLEANS_ADVERTISED_IP`: Set specific IP address for Orleans clustering (e.g., "192.168.1.100")
|
||||||
|
- `ASPNETCORE_ENVIRONMENT`: Set environment (Production/Development/etc.)
|
||||||
|
|
||||||
|
### 3. Network Configuration Improvements
|
||||||
|
|
||||||
|
The following improvements have been made to handle Docker networking issues:
|
||||||
|
|
||||||
|
1. **Endpoint Configuration**:
|
||||||
|
- `listenOnAnyHostAddress: true` allows binding to all network interfaces
|
||||||
|
- Increased timeout values for better reliability
|
||||||
|
|
||||||
|
2. **Fallback Mechanisms**:
|
||||||
|
- Automatic fallback to localhost clustering if database unavailable
|
||||||
|
- Memory storage fallback for grain persistence
|
||||||
|
|
||||||
|
3. **Improved Timeouts**:
|
||||||
|
- Response timeout: 60 seconds
|
||||||
|
- Probe timeout: 10 seconds
|
||||||
|
- Join attempt timeout: 120 seconds
|
||||||
|
|
||||||
|
### 4. Monitoring and Debugging
|
||||||
|
|
||||||
|
#### Orleans Dashboard
|
||||||
|
Available in development mode at: `http://localhost:9999`
|
||||||
|
- Username: admin
|
||||||
|
- Password: admin
|
||||||
|
|
||||||
|
#### Health Checks
|
||||||
|
Monitor application health at:
|
||||||
|
- `/health` - Full health check
|
||||||
|
- `/alive` - Basic liveness check
|
||||||
|
|
||||||
|
### 5. Emergency Procedures
|
||||||
|
|
||||||
|
If Orleans clustering is causing deployment issues:
|
||||||
|
|
||||||
|
1. **Immediate Fix**: Set environment variable `DISABLE_ORLEANS_CLUSTERING=true`
|
||||||
|
2. **Restart Services**: Restart the managing.api container
|
||||||
|
3. **Check Logs**: Monitor for connection timeout errors
|
||||||
|
4. **Database Check**: Verify PostgreSQL Orleans database connectivity
|
||||||
|
|
||||||
|
### 6. Database Requirements
|
||||||
|
|
||||||
|
Orleans requires these PostgreSQL databases:
|
||||||
|
- Main application database (from `PostgreSql:ConnectionString`)
|
||||||
|
- Orleans clustering database (from `PostgreSql:Orleans`)
|
||||||
|
|
||||||
|
If either is unavailable, the system will gracefully degrade functionality.
|
||||||
|
|
||||||
|
## Testing the Fix
|
||||||
|
|
||||||
|
1. Deploy with the updated configuration
|
||||||
|
2. Monitor logs for Orleans connection errors
|
||||||
|
3. Verify grain functionality through the dashboard (development) or API endpoints
|
||||||
|
4. Test failover scenarios by temporarily disabling database connectivity
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- `src/Managing.Bootstrap/ApiBootstrap.cs` - Orleans configuration
|
||||||
|
- `src/Managing.Docker/docker-compose.yml` - Docker networking
|
||||||
|
- `src/Managing.Api/appsettings.*.json` - Environment-specific settings
|
||||||
125
src/Managing.Api/TRADING_CONTROLLER_SECURITY.md
Normal file
125
src/Managing.Api/TRADING_CONTROLLER_SECURITY.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# TradingController Security Enhancement
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `InitPrivyWallet` endpoint in `TradingController` has been enhanced with admin role security. This ensures that only authorized users can initialize wallet addresses.
|
||||||
|
|
||||||
|
## Security Rules
|
||||||
|
|
||||||
|
### For Regular Users
|
||||||
|
- Can **only** initialize wallet addresses that they own
|
||||||
|
- The system verifies ownership by checking if the provided public address exists in one of the user's accounts
|
||||||
|
- If a user tries to initialize an address they don't own, they receive a `403 Forbidden` response
|
||||||
|
|
||||||
|
### For Admin Users
|
||||||
|
- Can initialize **any** wallet address in the system
|
||||||
|
- Admin status is determined by the `AdminUsers` environment variable
|
||||||
|
- All admin actions are logged for audit purposes
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
1. **User Authentication**: Endpoint requires valid JWT token
|
||||||
|
2. **Admin Check**: System checks if user is in the admin list via `IAdminConfigurationService`
|
||||||
|
3. **Ownership Verification**: For non-admin users, verifies address ownership via `IAccountService.GetAccountByKey()`
|
||||||
|
4. **Action Logging**: All operations are logged with user context
|
||||||
|
|
||||||
|
### Security Validation
|
||||||
|
```csharp
|
||||||
|
private async Task<bool> CanUserInitializeAddress(string userName, string publicAddress)
|
||||||
|
{
|
||||||
|
// Admin users can initialize any address
|
||||||
|
if (_adminService.IsUserAdmin(userName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular users can only initialize their own addresses
|
||||||
|
var account = await _accountService.GetAccountByKey(publicAddress, true, false);
|
||||||
|
return account?.User?.Name == userName;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Responses
|
||||||
|
|
||||||
|
### 401 Unauthorized
|
||||||
|
- Missing or invalid JWT token
|
||||||
|
- User not found in system
|
||||||
|
|
||||||
|
### 403 Forbidden
|
||||||
|
- Non-admin user trying to initialize address they don't own
|
||||||
|
- Message: "You don't have permission to initialize this wallet address. You can only initialize your own wallet addresses."
|
||||||
|
|
||||||
|
### 400 Bad Request
|
||||||
|
- Empty or null public address provided
|
||||||
|
|
||||||
|
### 500 Internal Server Error
|
||||||
|
- System error during wallet initialization
|
||||||
|
- Database connectivity issues
|
||||||
|
- External service failures
|
||||||
|
|
||||||
|
## Admin Configuration
|
||||||
|
|
||||||
|
Set admin users via environment variable:
|
||||||
|
```bash
|
||||||
|
AdminUsers=admin1,admin2,superuser
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audit Logging
|
||||||
|
|
||||||
|
All operations are logged with appropriate context:
|
||||||
|
|
||||||
|
**Admin Operations:**
|
||||||
|
```
|
||||||
|
Admin user {UserName} initializing address {Address}
|
||||||
|
```
|
||||||
|
|
||||||
|
**User Operations:**
|
||||||
|
```
|
||||||
|
User {UserName} initializing their own address {Address}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Security Violations:**
|
||||||
|
```
|
||||||
|
User {UserName} attempted to initialize address {Address} that doesn't belong to them
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Regular User - Own Address
|
||||||
|
```bash
|
||||||
|
POST /Trading/InitPrivyWallet
|
||||||
|
Authorization: Bearer {user-jwt-token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
"0x1234567890123456789012345678901234567890"
|
||||||
|
```
|
||||||
|
**Result**: ✅ Success (if address belongs to user)
|
||||||
|
|
||||||
|
### Regular User - Other's Address
|
||||||
|
```bash
|
||||||
|
POST /Trading/InitPrivyWallet
|
||||||
|
Authorization: Bearer {user-jwt-token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
||||||
|
```
|
||||||
|
**Result**: ❌ 403 Forbidden
|
||||||
|
|
||||||
|
### Admin User - Any Address
|
||||||
|
```bash
|
||||||
|
POST /Trading/InitPrivyWallet
|
||||||
|
Authorization: Bearer {admin-jwt-token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
||||||
|
```
|
||||||
|
**Result**: ✅ Success (admin can initialize any address)
|
||||||
|
|
||||||
|
## Security Benefits
|
||||||
|
|
||||||
|
1. **Prevents Unauthorized Access**: Users cannot initialize wallets they don't own
|
||||||
|
2. **Admin Oversight**: Admins can manage any wallet for system administration
|
||||||
|
3. **Audit Trail**: All actions are logged for compliance and security monitoring
|
||||||
|
4. **Clear Authorization**: Explicit permission checks with meaningful error messages
|
||||||
|
5. **Secure Configuration**: Admin privileges configured via environment variables, not API calls
|
||||||
@@ -31,5 +31,18 @@
|
|||||||
"ButtonExpirationMinutes": 2
|
"ButtonExpirationMinutes": 2
|
||||||
},
|
},
|
||||||
"RunOrleansGrains": true,
|
"RunOrleansGrains": true,
|
||||||
|
"WorkerPricesFifteenMinutes": false,
|
||||||
|
"WorkerPricesOneHour": false,
|
||||||
|
"WorkerPricesFourHours": false,
|
||||||
|
"WorkerPricesOneDay": false,
|
||||||
|
"WorkerPricesFiveMinutes": false,
|
||||||
|
"WorkerSpotlight": false,
|
||||||
|
"WorkerTraderWatcher": false,
|
||||||
|
"WorkerLeaderboard": false,
|
||||||
|
"WorkerFundingRatesWatcher": false,
|
||||||
|
"WorkerGeneticAlgorithm": false,
|
||||||
|
"WorkerBundleBacktest": true,
|
||||||
|
"WorkerBalancesTracking": false,
|
||||||
|
"WorkerNotifyBundleBacktest": false,
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,23 @@
|
|||||||
},
|
},
|
||||||
"RunOrleansGrains": true,
|
"RunOrleansGrains": true,
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
|
"KAIGEN_SECRET_KEY": "KaigenXCowchain",
|
||||||
|
"KAIGEN_CREDITS_ENABLED": false,
|
||||||
"WorkerBotManager": true,
|
"WorkerBotManager": true,
|
||||||
"WorkerBalancesTracking": false,
|
"WorkerBalancesTracking": false,
|
||||||
"WorkerNotifyBundleBacktest": false,
|
"WorkerNotifyBundleBacktest": false,
|
||||||
"KAIGEN_SECRET_KEY": "KaigenXCowchain",
|
"WorkerPricesFifteenMinutes": true,
|
||||||
"KAIGEN_CREDITS_ENABLED": false
|
"WorkerPricesOneHour": false,
|
||||||
|
"WorkerPricesFourHours": false,
|
||||||
|
"WorkerPricesOneDay": false,
|
||||||
|
"WorkerPricesFiveMinutes": false,
|
||||||
|
"WorkerFee": false,
|
||||||
|
"WorkerPositionManager": false,
|
||||||
|
"WorkerPositionFetcher": false,
|
||||||
|
"WorkerSpotlight": false,
|
||||||
|
"WorkerTraderWatcher": false,
|
||||||
|
"WorkerLeaderboard": false,
|
||||||
|
"WorkerFundingRatesWatcher": false,
|
||||||
|
"WorkerGeneticAlgorithm": false,
|
||||||
|
"WorkerBundleBacktest": false
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,11 @@
|
|||||||
},
|
},
|
||||||
"RunOrleansGrains": true,
|
"RunOrleansGrains": true,
|
||||||
"DeploymentMode": false,
|
"DeploymentMode": false,
|
||||||
|
"Orleans": {
|
||||||
|
"EnableClustering": true,
|
||||||
|
"ConnectionTimeout": 60,
|
||||||
|
"MaxJoinAttempts": 3
|
||||||
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"WorkerBotManager": true,
|
"WorkerBotManager": true,
|
||||||
"WorkerBalancesTracking": true,
|
"WorkerBalancesTracking": true,
|
||||||
|
|||||||
@@ -68,5 +68,19 @@
|
|||||||
},
|
},
|
||||||
"RunOrleansGrains": true,
|
"RunOrleansGrains": true,
|
||||||
"DeploymentMode": false,
|
"DeploymentMode": false,
|
||||||
|
"WorkerPricesFifteenMinutes": false,
|
||||||
|
"WorkerPricesOneHour": false,
|
||||||
|
"WorkerPricesFourHours": false,
|
||||||
|
"WorkerPricesOneDay": false,
|
||||||
|
"WorkerPricesFiveMinutes": false,
|
||||||
|
"WorkerSpotlight": false,
|
||||||
|
"WorkerTraderWatcher": false,
|
||||||
|
"WorkerLeaderboard": false,
|
||||||
|
"WorkerFundingRatesWatcher": false,
|
||||||
|
"WorkerGeneticAlgorithm": false,
|
||||||
|
"WorkerBundleBacktest": false,
|
||||||
|
"WorkerBalancesTracking": false,
|
||||||
|
"WorkerNotifyBundleBacktest": false,
|
||||||
|
"AdminUsers": "",
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
@@ -24,5 +24,5 @@ public interface ICandleRepository
|
|||||||
Enums.TradingExchanges exchange,
|
Enums.TradingExchanges exchange,
|
||||||
Enums.Timeframe timeframe,
|
Enums.Timeframe timeframe,
|
||||||
DateTime start);
|
DateTime start);
|
||||||
void InsertCandle(Candle candle);
|
Task InsertCandle(Candle candle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,11 +97,11 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
if (botStatus == BotStatus.Running && _tradingBot == null)
|
if (botStatus == BotStatus.Running && _tradingBot == null)
|
||||||
{
|
{
|
||||||
// Now, we can proceed with resuming the bot.
|
// Now, we can proceed with resuming the bot.
|
||||||
await ResumeBotInternalAsync();
|
await ResumeBotInternalAsync(botStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ResumeBotInternalAsync()
|
private async Task ResumeBotInternalAsync(BotStatus previousStatus)
|
||||||
{
|
{
|
||||||
// Idempotency check
|
// Idempotency check
|
||||||
if (_tradingBot != null)
|
if (_tradingBot != null)
|
||||||
@@ -113,7 +113,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
{
|
{
|
||||||
// Create and initialize trading bot instance
|
// Create and initialize trading bot instance
|
||||||
_tradingBot = CreateTradingBotInstance(_state.State.Config);
|
_tradingBot = CreateTradingBotInstance(_state.State.Config);
|
||||||
await _tradingBot.Start();
|
await _tradingBot.Start(previousStatus);
|
||||||
|
|
||||||
// Set startup time when bot actually starts running
|
// Set startup time when bot actually starts running
|
||||||
_state.State.StartupTime = DateTime.UtcNow;
|
_state.State.StartupTime = DateTime.UtcNow;
|
||||||
@@ -155,7 +155,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Resume the bot - this handles registry status update internally
|
// Resume the bot - this handles registry status update internally
|
||||||
await ResumeBotInternalAsync();
|
await ResumeBotInternalAsync(status);
|
||||||
_logger.LogInformation("LiveTradingBotGrain {GrainId} started successfully", this.GetPrimaryKey());
|
_logger.LogInformation("LiveTradingBotGrain {GrainId} started successfully", this.GetPrimaryKey());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(config.Timeframe);
|
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(config.Timeframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start()
|
public async Task Start(BotStatus previousStatus)
|
||||||
{
|
{
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
@@ -77,27 +77,37 @@ public class TradingBotBase : ITradingBot
|
|||||||
// await CancelAllOrders();
|
// await CancelAllOrders();
|
||||||
|
|
||||||
// Send startup message only for fresh starts (not reboots)
|
// Send startup message only for fresh starts (not reboots)
|
||||||
if (!true)
|
switch (previousStatus)
|
||||||
{
|
{
|
||||||
var indicatorNames = Config.Scenario.Indicators.Select(i => i.Type.ToString()).ToList();
|
case BotStatus.Saved:
|
||||||
var startupMessage = $"🚀 **Bot Started Successfully!**\n\n" +
|
var indicatorNames = Config.Scenario.Indicators.Select(i => i.Type.ToString()).ToList();
|
||||||
$"📊 **Trading Setup:**\n" +
|
var startupMessage = $"🚀 **Bot Started Successfully!**\n\n" +
|
||||||
$"🎯 Ticker: `{Config.Ticker}`\n" +
|
$"📊 **Trading Setup:**\n" +
|
||||||
$"⏰ Timeframe: `{Config.Timeframe}`\n" +
|
$"🎯 Ticker: `{Config.Ticker}`\n" +
|
||||||
$"🎮 Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" +
|
$"⏰ Timeframe: `{Config.Timeframe}`\n" +
|
||||||
$"💰 Balance: `${Config.BotTradingBalance:F2}`\n" +
|
$"🎮 Scenario: `{Config.Scenario?.Name ?? "Unknown"}`\n" +
|
||||||
$"👀 Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" +
|
$"💰 Balance: `${Config.BotTradingBalance:F2}`\n" +
|
||||||
$"📈 **Active Indicators:** `{string.Join(", ", indicatorNames)}`\n\n" +
|
$"👀 Mode: `{(Config.IsForWatchingOnly ? "Watch Only" : "Live Trading")}`\n\n" +
|
||||||
$"✅ Ready to monitor signals and execute trades!\n" +
|
$"📈 **Active Indicators:** `{string.Join(", ", indicatorNames)}`\n\n" +
|
||||||
$"📢 I'll notify you when signals are triggered.";
|
$"✅ Ready to monitor signals and execute trades!\n" +
|
||||||
|
$"📢 I'll notify you when signals are triggered.";
|
||||||
|
|
||||||
await LogInformation(startupMessage);
|
await LogInformation(startupMessage);
|
||||||
}
|
break;
|
||||||
else
|
|
||||||
{
|
case BotStatus.Running:
|
||||||
await LogInformation($"🔄 **Bot Restarted**\n" +
|
return;
|
||||||
$"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" +
|
|
||||||
$"✅ Ready to continue trading");
|
case BotStatus.Stopped:
|
||||||
|
// If status was Stopped we log a message to inform the user that the bot is restarting
|
||||||
|
await LogInformation($"🔄 **Bot Restarted**\n" +
|
||||||
|
$"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" +
|
||||||
|
$"✅ Ready to continue trading");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Handle any other status if needed
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -160,10 +170,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
ExecutionCount++;
|
ExecutionCount++;
|
||||||
|
|
||||||
Logger.LogInformation($"Signals : {Signals.Count}");
|
Logger.LogInformation("Bot Status - ServerDate: {ServerDate}, LastCandleDate: {LastCandleDate}, Signals: {SignalCount}, Executions: {ExecutionCount}, Positions: {PositionCount}",
|
||||||
Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
|
DateTime.UtcNow, LastCandle.Date, Signals.Count, ExecutionCount, Positions.Count);
|
||||||
Logger.LogInformation($"Positions : {Positions.Count}");
|
|
||||||
Logger.LogInformation("__________________________________________________");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,22 +389,26 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Notify platform summary about the executed trade
|
// Notify platform summary about the executed trade
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory,
|
||||||
{
|
async grainFactory =>
|
||||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
|
||||||
var tradeExecutedEvent = new TradeExecutedEvent
|
|
||||||
{
|
{
|
||||||
TradeId = position.Identifier,
|
var platformGrain =
|
||||||
Ticker = position.Ticker,
|
grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
||||||
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage
|
var tradeExecutedEvent = new TradeExecutedEvent
|
||||||
};
|
{
|
||||||
|
TradeId = position.Identifier,
|
||||||
|
Ticker = position.Ticker,
|
||||||
|
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage
|
||||||
|
};
|
||||||
|
|
||||||
await platformGrain.OnTradeExecutedAsync(tradeExecutedEvent);
|
await platformGrain.OnTradeExecutedAsync(tradeExecutedEvent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "Failed to notify platform summary about trade execution for position {PositionId}", position.Identifier);
|
Logger.LogWarning(ex,
|
||||||
|
"Failed to notify platform summary about trade execution for position {PositionId}",
|
||||||
|
position.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,7 +991,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
_scopeFactory, async (exchangeService, accountService, tradingService) =>
|
_scopeFactory, async (exchangeService, accountService, tradingService) =>
|
||||||
{
|
{
|
||||||
closedPosition =
|
closedPosition =
|
||||||
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService, _scopeFactory)
|
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService,
|
||||||
|
_scopeFactory)
|
||||||
.Handle(command);
|
.Handle(command);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1024,35 +1037,39 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
if (currentCandle != null)
|
if (currentCandle != null)
|
||||||
{
|
{
|
||||||
List<Candle> recentCandles = null;
|
List<Candle> recentCandles = null;
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
||||||
{
|
|
||||||
recentCandles = Config.IsForBacktest
|
|
||||||
? (LastCandle != null ? new List<Candle>() { LastCandle } : new List<Candle>())
|
|
||||||
: (await exchangeService.GetCandlesInflux(TradingExchanges.Evm, Config.Ticker,
|
|
||||||
DateTime.UtcNow.AddHours(-4), Config.Timeframe)).ToList();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if we have any candles before proceeding
|
|
||||||
if (recentCandles == null || !recentCandles.Any())
|
|
||||||
{
|
|
||||||
await LogWarning($"No recent candles available for position {position.Identifier}. Using current candle data instead.");
|
|
||||||
|
|
||||||
// Fallback to current candle if available
|
|
||||||
if (currentCandle != null)
|
|
||||||
{
|
{
|
||||||
recentCandles = new List<Candle> { currentCandle };
|
recentCandles = Config.IsForBacktest
|
||||||
}
|
? (LastCandle != null ? new List<Candle>() { LastCandle } : new List<Candle>())
|
||||||
else
|
: (await exchangeService.GetCandlesInflux(TradingExchanges.Evm, Config.Ticker,
|
||||||
{
|
DateTime.UtcNow.AddHours(-4), Config.Timeframe)).ToList();
|
||||||
await LogWarning($"No candle data available for position {position.Identifier}. Cannot determine stop loss/take profit hit.");
|
});
|
||||||
Logger.LogError("No candle data available for position {PositionId}. Cannot determine stop loss/take profit hit.", position.Identifier);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var minPriceRecent = recentCandles.Min(c => c.Low);
|
// Check if we have any candles before proceeding
|
||||||
var maxPriceRecent = recentCandles.Max(c => c.High);
|
if (recentCandles == null || !recentCandles.Any())
|
||||||
|
{
|
||||||
|
await LogWarning(
|
||||||
|
$"No recent candles available for position {position.Identifier}. Using current candle data instead.");
|
||||||
|
|
||||||
|
// Fallback to current candle if available
|
||||||
|
if (currentCandle != null)
|
||||||
|
{
|
||||||
|
recentCandles = new List<Candle> { currentCandle };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LogWarning(
|
||||||
|
$"No candle data available for position {position.Identifier}. Cannot determine stop loss/take profit hit.");
|
||||||
|
Logger.LogError(
|
||||||
|
"No candle data available for position {PositionId}. Cannot determine stop loss/take profit hit.",
|
||||||
|
position.Identifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var minPriceRecent = recentCandles.Min(c => c.Low);
|
||||||
|
var maxPriceRecent = recentCandles.Max(c => c.High);
|
||||||
|
|
||||||
bool wasStopLossHit = false;
|
bool wasStopLossHit = false;
|
||||||
bool wasTakeProfitHit = false;
|
bool wasTakeProfitHit = false;
|
||||||
|
|||||||
55
src/Managing.Application/Shared/AdminConfigurationService.cs
Normal file
55
src/Managing.Application/Shared/AdminConfigurationService.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Managing.Application.Shared;
|
||||||
|
|
||||||
|
public interface IAdminConfigurationService
|
||||||
|
{
|
||||||
|
bool IsUserAdmin(string userName);
|
||||||
|
List<string> GetAdminUserNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AdminConfigurationService : IAdminConfigurationService
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly ILogger<AdminConfigurationService> _logger;
|
||||||
|
|
||||||
|
public AdminConfigurationService(IConfiguration configuration, ILogger<AdminConfigurationService> logger)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsUserAdmin(string userName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(userName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var adminUserNames = GetAdminUserNames();
|
||||||
|
var isAdmin = adminUserNames.Contains(userName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (isAdmin)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User {UserName} has admin privileges", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetAdminUserNames()
|
||||||
|
{
|
||||||
|
var adminUsers = _configuration["AdminUsers"];
|
||||||
|
if (string.IsNullOrEmpty(adminUsers))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No admin users configured. Set AdminUsers environment variable.");
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return adminUsers.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(u => u.Trim())
|
||||||
|
.Where(u => !string.IsNullOrEmpty(u))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,39 +39,35 @@ public class PricesService : IPricesService
|
|||||||
throw new Exception($"Enable to found account for exchange {exchange}");
|
throw new Exception($"Enable to found account for exchange {exchange}");
|
||||||
|
|
||||||
var lastCandles =
|
var lastCandles =
|
||||||
await _candleRepository.GetCandles(exchange, ticker, timeframe, DateTime.UtcNow.AddDays(-2));
|
await _candleRepository.GetCandles(exchange, ticker, timeframe, DateTime.UtcNow.AddDays(-30), limit: 1)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
var lastCandle = lastCandles.LastOrDefault();
|
var lastCandle = lastCandles.LastOrDefault();
|
||||||
var startDate = lastCandle != null ? lastCandle.Date : new DateTime(2017, 1, 1);
|
var startDate = lastCandle != null ? lastCandle.Date : new DateTime(2017, 1, 1);
|
||||||
|
|
||||||
List<Candle> newCandles;
|
List<Candle> newCandles =
|
||||||
if (!lastCandles.Any())
|
await _exchangeService.GetCandles(account, ticker, startDate, timeframe, true)
|
||||||
{
|
.ConfigureAwait(false);
|
||||||
newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, true);
|
|
||||||
}
|
var candlesToInsert = lastCandle == null
|
||||||
else
|
? newCandles
|
||||||
{
|
: newCandles.Where(c => c.Date > lastCandle.Date);
|
||||||
newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var candles = !lastCandles.Any() ? newCandles : newCandles.Where(c => c.Date > lastCandle?.Date);
|
|
||||||
var candlesInserted = 0;
|
var candlesInserted = 0;
|
||||||
|
|
||||||
foreach (var newCandle in candles)
|
foreach (var newCandle in candlesToInsert)
|
||||||
{
|
{
|
||||||
if (lastCandle == null || newCandle.Date > lastCandle.Date)
|
await _candleRepository.InsertCandle(newCandle).ConfigureAwait(false);
|
||||||
{
|
candlesInserted++;
|
||||||
_candleRepository.InsertCandle(newCandle);
|
|
||||||
candlesInserted++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candlesInserted > 0)
|
if (candlesInserted > 0)
|
||||||
_logger.LogInformation($"[{exchange}][{ticker}][{timeframe}] New candles inserted : {candlesInserted}");
|
_logger.LogInformation("[{exchange}][{ticker}][{timeframe}] New candles inserted : {candlesInserted}", exchange, ticker, timeframe, candlesInserted);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
SentrySdk.CaptureException(ex);
|
SentrySdk.CaptureException(ex);
|
||||||
_logger.LogError($"[{exchange}][{ticker}][{timeframe}] Error : {ex.Message} | {ex.StackTrace}");
|
_logger.LogError("[{exchange}][{ticker}][{timeframe}] Error : {ex.Message} | {ex.StackTrace}", exchange, ticker, timeframe, ex.Message, ex.StackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Managing.Api.Workers\Managing.Api.Workers.csproj"/>
|
|
||||||
<ProjectReference Include="..\Managing.Api\Managing.Api.csproj"/>
|
<ProjectReference Include="..\Managing.Api\Managing.Api.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
using Projects;
|
||||||
|
|
||||||
var builder = DistributedApplication.CreateBuilder(args);
|
var builder = DistributedApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add API projects
|
// Add API projects
|
||||||
var managingApi = builder.AddProject<Projects.Managing_Api>("managing-api");
|
var managingApi = builder.AddProject<Managing_Api>("managing-api");
|
||||||
var workersApi = builder.AddProject<Projects.Managing_Api_Workers>("worker-api");
|
|
||||||
|
|
||||||
// No need to add containers - your APIs will use their existing connection strings
|
// No need to add containers - your APIs will use their existing connection strings
|
||||||
// from their respective appsettings.json files
|
// from their respective appsettings.json files
|
||||||
|
|
||||||
// Connect services to resources
|
// Connect services to resources
|
||||||
workersApi.WithReference(managingApi);
|
|
||||||
|
|
||||||
builder.Build().Run();
|
builder.Build().Run();
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@@ -87,21 +88,33 @@ public static class ApiBootstrap
|
|||||||
runOrleansGrains = 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;
|
||||||
|
|
||||||
var postgreSqlConnectionString = configuration.GetSection("PostgreSql")["Orleans"];
|
var postgreSqlConnectionString = configuration.GetSection("PostgreSql")["Orleans"];
|
||||||
|
|
||||||
return hostBuilder.UseOrleans(siloBuilder =>
|
return hostBuilder.UseOrleans(siloBuilder =>
|
||||||
{
|
{
|
||||||
// Configure clustering with improved networking
|
// Configure clustering with improved networking or use localhost clustering if disabled
|
||||||
siloBuilder
|
if (!disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
|
||||||
.UseAdoNetClustering(options =>
|
{
|
||||||
{
|
siloBuilder
|
||||||
options.ConnectionString = postgreSqlConnectionString;
|
.UseAdoNetClustering(options =>
|
||||||
options.Invariant = "Npgsql";
|
{
|
||||||
});
|
options.ConnectionString = postgreSqlConnectionString;
|
||||||
|
options.Invariant = "Npgsql";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback to localhost clustering for testing or when database is unavailable
|
||||||
|
siloBuilder.UseLocalhostClustering();
|
||||||
|
}
|
||||||
|
|
||||||
// Conditionally configure reminder service based on flag
|
// Conditionally configure reminder service based on flag
|
||||||
if (runOrleansGrains)
|
if (runOrleansGrains && !disableOrleansClustering && !string.IsNullOrEmpty(postgreSqlConnectionString))
|
||||||
{
|
{
|
||||||
siloBuilder.UseAdoNetReminderService(options =>
|
siloBuilder.UseAdoNetReminderService(options =>
|
||||||
{
|
{
|
||||||
@@ -110,9 +123,28 @@ public static class ApiBootstrap
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure networking for better silo communication
|
// Configure networking - simplified for Docker compatibility
|
||||||
|
if (disableOrleansClustering)
|
||||||
|
{
|
||||||
|
// Use localhost clustering when clustering is disabled
|
||||||
|
siloBuilder.ConfigureEndpoints(IPAddress.Loopback, 11111, 30000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use localhost for development, proper hostname for production
|
||||||
|
if (isProduction)
|
||||||
|
{
|
||||||
|
// In production, use all interfaces
|
||||||
|
siloBuilder.ConfigureEndpoints(IPAddress.Any, 11111, 30000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// In development, use localhost
|
||||||
|
siloBuilder.ConfigureEndpoints(IPAddress.Loopback, 11111, 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
siloBuilder
|
siloBuilder
|
||||||
.ConfigureEndpoints(siloPort: 11111, gatewayPort: 30000)
|
|
||||||
.Configure<ClusterOptions>(options =>
|
.Configure<ClusterOptions>(options =>
|
||||||
{
|
{
|
||||||
// Configure cluster options with unique identifiers
|
// Configure cluster options with unique identifiers
|
||||||
@@ -121,8 +153,26 @@ public static class ApiBootstrap
|
|||||||
})
|
})
|
||||||
.Configure<MessagingOptions>(options =>
|
.Configure<MessagingOptions>(options =>
|
||||||
{
|
{
|
||||||
// Configure messaging for better reliability
|
// Configure messaging for better reliability with increased timeouts
|
||||||
options.ResponseTimeout = TimeSpan.FromSeconds(30);
|
options.ResponseTimeout = TimeSpan.FromSeconds(60);
|
||||||
|
options.DropExpiredMessages = true;
|
||||||
|
})
|
||||||
|
.Configure<ClusterMembershipOptions>(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<GatewayOptions>(options =>
|
||||||
|
{
|
||||||
|
// Configure gateway with improved timeouts
|
||||||
|
options.GatewayListRefreshPeriod = TimeSpan.FromSeconds(60);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Conditionally configure grain execution based on flag
|
// Conditionally configure grain execution based on flag
|
||||||
@@ -165,27 +215,42 @@ public static class ApiBootstrap
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
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";
|
|
||||||
})
|
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
// Register existing services for Orleans DI
|
// Register existing services for Orleans DI
|
||||||
@@ -215,6 +280,7 @@ public static class ApiBootstrap
|
|||||||
services.AddScoped<IWorkerService, WorkerService>();
|
services.AddScoped<IWorkerService, WorkerService>();
|
||||||
services.AddScoped<ISynthPredictionService, SynthPredictionService>();
|
services.AddScoped<ISynthPredictionService, SynthPredictionService>();
|
||||||
services.AddScoped<ISynthApiClient, SynthApiClient>();
|
services.AddScoped<ISynthApiClient, SynthApiClient>();
|
||||||
|
services.AddScoped<IPricesService, PricesService>();
|
||||||
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
||||||
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
||||||
|
|
||||||
@@ -235,6 +301,9 @@ public static class ApiBootstrap
|
|||||||
services.AddSingleton<IMessengerService, MessengerService>();
|
services.AddSingleton<IMessengerService, MessengerService>();
|
||||||
services.AddSingleton<IDiscordService, DiscordService>();
|
services.AddSingleton<IDiscordService, DiscordService>();
|
||||||
|
|
||||||
|
// Admin services
|
||||||
|
services.AddSingleton<IAdminConfigurationService, AdminConfigurationService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +361,7 @@ public static class ApiBootstrap
|
|||||||
|
|
||||||
private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
|
private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
// Balance Workers
|
||||||
if (configuration.GetValue<bool>("WorkerBalancesTracking", false))
|
if (configuration.GetValue<bool>("WorkerBalancesTracking", false))
|
||||||
{
|
{
|
||||||
services.AddHostedService<BalanceTrackingWorker>();
|
services.AddHostedService<BalanceTrackingWorker>();
|
||||||
@@ -302,6 +372,63 @@ public static class ApiBootstrap
|
|||||||
services.AddHostedService<NotifyBundleBacktestWorker>();
|
services.AddHostedService<NotifyBundleBacktestWorker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Price Workers
|
||||||
|
if (configuration.GetValue<bool>("WorkerPricesFifteenMinutes", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<PricesFifteenMinutesWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerPricesOneHour", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<PricesOneHourWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerPricesFourHours", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<PricesFourHoursWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerPricesOneDay", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<PricesOneDayWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerPricesFiveMinutes", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<PricesFiveMinutesWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other Workers
|
||||||
|
if (configuration.GetValue<bool>("WorkerSpotlight", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<SpotlightWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerTraderWatcher", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<TraderWatcher>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerLeaderboard", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<LeaderboardWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerFundingRatesWatcher", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<FundingRatesWatcher>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerGeneticAlgorithm", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<GeneticAlgorithmWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("WorkerBundleBacktest", false))
|
||||||
|
{
|
||||||
|
services.AddHostedService<BundleBacktestWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
using Managing.Application;
|
|
||||||
using Managing.Application.Abstractions;
|
|
||||||
using Managing.Application.Abstractions.Repositories;
|
|
||||||
using Managing.Application.Abstractions.Services;
|
|
||||||
using Managing.Application.Accounts;
|
|
||||||
using Managing.Application.Backtests;
|
|
||||||
using Managing.Application.ManageBot;
|
|
||||||
using Managing.Application.MoneyManagements;
|
|
||||||
using Managing.Application.Scenarios;
|
|
||||||
using Managing.Application.Shared;
|
|
||||||
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.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 Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Managing.Bootstrap;
|
|
||||||
|
|
||||||
public static class WorkersBootstrap
|
|
||||||
{
|
|
||||||
public static IServiceCollection RegisterWorkersDependencies(this IServiceCollection services,
|
|
||||||
IConfiguration configuration)
|
|
||||||
{
|
|
||||||
return services
|
|
||||||
.AddApplication()
|
|
||||||
.AddInfrastructure(configuration)
|
|
||||||
.AddWorkers(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IServiceCollection AddApplication(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddScoped<ITradingService, TradingService>();
|
|
||||||
services.AddScoped<IScenarioService, ScenarioService>();
|
|
||||||
services.AddScoped<IMoneyManagementService, MoneyManagementService>();
|
|
||||||
services.AddScoped<IAccountService, AccountService>();
|
|
||||||
services.AddScoped<IStatisticService, StatisticService>();
|
|
||||||
services.AddScoped<ISettingsService, SettingsService>();
|
|
||||||
services.AddScoped<IUserService, UserService>();
|
|
||||||
services.AddScoped<IGeneticService, GeneticService>();
|
|
||||||
services.AddScoped<IBotService, BotService>();
|
|
||||||
services.AddScoped<IWorkerService, WorkerService>();
|
|
||||||
services.AddScoped<ISynthPredictionService, SynthPredictionService>();
|
|
||||||
services.AddScoped<ISynthApiClient, SynthApiClient>();
|
|
||||||
services.AddScoped<IPricesService, PricesService>();
|
|
||||||
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
|
||||||
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
|
||||||
|
|
||||||
// Processors
|
|
||||||
services.AddTransient<IBacktester, Backtester>();
|
|
||||||
services.AddTransient<IExchangeProcessor, EvmProcessor>();
|
|
||||||
|
|
||||||
services.AddTransient<ITradaoService, TradaoService>();
|
|
||||||
services.AddTransient<IExchangeService, ExchangeService>();
|
|
||||||
services.AddTransient<IExchangeStream, ExchangeStream>();
|
|
||||||
|
|
||||||
|
|
||||||
services.AddTransient<IPrivyService, PrivyService>();
|
|
||||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
|
||||||
services.AddTransient<IWebhookService, WebhookService>();
|
|
||||||
services.AddTransient<IKaigenService, KaigenService>();
|
|
||||||
|
|
||||||
services.AddSingleton<IMessengerService, MessengerService>();
|
|
||||||
services.AddSingleton<IDiscordService, DiscordService>();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
// Price Workers
|
|
||||||
if (configuration.GetValue<bool>("WorkerPricesFifteenMinutes", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<PricesFifteenMinutesWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerPricesOneHour", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<PricesOneHourWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerPricesFourHours", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<PricesFourHoursWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerPricesOneDay", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<PricesOneDayWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerPricesFiveMinutes", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<PricesFiveMinutesWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerSpotlight", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<SpotlightWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerTraderWatcher", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<TraderWatcher>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerLeaderboard", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<LeaderboardWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerFundingRatesWatcher", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<FundingRatesWatcher>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerGeneticAlgorithm", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<GeneticAlgorithmWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerBundleBacktest", false))
|
|
||||||
{
|
|
||||||
services.AddHostedService<BundleBacktestWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
// Database
|
|
||||||
services.AddSingleton<IPostgreSqlSettings>(sp =>
|
|
||||||
sp.GetRequiredService<IOptions<PostgreSqlSettings>>().Value);
|
|
||||||
|
|
||||||
services.AddSingleton<IInfluxDbSettings>(sp =>
|
|
||||||
sp.GetRequiredService<IOptions<InfluxDbSettings>>().Value);
|
|
||||||
|
|
||||||
services.AddTransient<IInfluxDbRepository, InfluxDbRepository>();
|
|
||||||
|
|
||||||
services.AddSingleton<IPrivySettings>(sp =>
|
|
||||||
sp.GetRequiredService<IOptions<PrivySettings>>().Value);
|
|
||||||
|
|
||||||
// Evm
|
|
||||||
services.AddUniswapV2();
|
|
||||||
services.AddGbcFeed();
|
|
||||||
services.AddChainlink();
|
|
||||||
services.AddChainlinkGmx();
|
|
||||||
services.AddSingleton<IEvmManager, EvmManager>();
|
|
||||||
|
|
||||||
// Repositories
|
|
||||||
services.AddTransient<ICandleRepository, CandleRepository>();
|
|
||||||
services.AddTransient<IAgentBalanceRepository, AgentBalanceRepository>();
|
|
||||||
services.AddTransient<IWorkerRepository, PostgreSqlWorkerRepository>();
|
|
||||||
services.AddTransient<IStatisticRepository, PostgreSqlStatisticRepository>();
|
|
||||||
services.AddTransient<ICandleRepository, CandleRepository>();
|
|
||||||
services.AddTransient<IAccountRepository, PostgreSqlAccountRepository>();
|
|
||||||
services.AddTransient<ISettingsRepository, PostgreSqlSettingsRepository>();
|
|
||||||
services.AddTransient<ITradingRepository, PostgreSqlTradingRepository>();
|
|
||||||
services.AddTransient<IBacktestRepository, PostgreSqlBacktestRepository>();
|
|
||||||
services.AddTransient<IBotRepository, PostgreSqlBotRepository>();
|
|
||||||
services.AddTransient<IUserRepository, PostgreSqlUserRepository>();
|
|
||||||
services.AddTransient<ISynthRepository, PostgreSqlSynthRepository>();
|
|
||||||
services.AddTransient<IGeneticRepository, PostgreSqlGeneticRepository>();
|
|
||||||
|
|
||||||
// Cache
|
|
||||||
services.AddDistributedMemoryCache();
|
|
||||||
services.AddTransient<ICacheService, CacheService>();
|
|
||||||
services.AddTransient<ITaskCache, TaskCache>();
|
|
||||||
|
|
||||||
|
|
||||||
// Processors
|
|
||||||
services.AddTransient<IExchangeProcessor, EvmProcessor>();
|
|
||||||
|
|
||||||
// Web Clients
|
|
||||||
services.AddTransient<ITradaoService, TradaoService>();
|
|
||||||
services.AddTransient<IExchangeService, ExchangeService>();
|
|
||||||
services.AddSingleton<IPrivyService, PrivyService>();
|
|
||||||
services.AddSingleton<ISynthApiClient, SynthApiClient>();
|
|
||||||
|
|
||||||
// Web3Proxy Configuration
|
|
||||||
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
|
||||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
|
||||||
|
|
||||||
// Http Clients
|
|
||||||
services.AddHttpClient();
|
|
||||||
|
|
||||||
// Messengers
|
|
||||||
services.AddSingleton<IMessengerService, MessengerService>();
|
|
||||||
services.AddSingleton<IDiscordService, DiscordService>();
|
|
||||||
services.AddSingleton<IWebhookService, WebhookService>();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,29 +17,7 @@ services:
|
|||||||
- /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro
|
- /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro
|
||||||
- /Users/oda/Microsoft/UserSecrets:/root/.microsoft/usersecrets/$USER_SECRETS_ID
|
- /Users/oda/Microsoft/UserSecrets:/root/.microsoft/usersecrets/$USER_SECRETS_ID
|
||||||
depends_on:
|
depends_on:
|
||||||
- managingdb
|
- postgres
|
||||||
|
|
||||||
managing.api.workers:
|
|
||||||
environment:
|
|
||||||
- ASPNETCORE_ENVIRONMENT=Oda-docker
|
|
||||||
- ASPNETCORE_URLS=https://+:443;http://+:80
|
|
||||||
- ASPNETCORE_Kestrel__Certificates__Default__Password=!Managing94
|
|
||||||
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/managing_cert.pfx
|
|
||||||
ports:
|
|
||||||
- "81:80"
|
|
||||||
- "444:443"
|
|
||||||
volumes:
|
|
||||||
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
|
|
||||||
- /Users/oda/ASP.NET/Https:/root/.aspnet/https:ro
|
|
||||||
depends_on:
|
|
||||||
- managingdb
|
|
||||||
|
|
||||||
managingdb:
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- mongodata:/data/db
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17.5
|
image: postgres:17.5
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
version: '3.4'
|
version: '3.4'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
managingdb:
|
|
||||||
image: mongo
|
|
||||||
networks:
|
|
||||||
- managing-network
|
|
||||||
|
|
||||||
managing.api:
|
managing.api:
|
||||||
image: ${DOCKER_REGISTRY-}managingapi
|
image: ${DOCKER_REGISTRY-}managingapi
|
||||||
build:
|
build:
|
||||||
@@ -13,14 +8,13 @@ services:
|
|||||||
dockerfile: Managing.Api/Dockerfile
|
dockerfile: Managing.Api/Dockerfile
|
||||||
networks:
|
networks:
|
||||||
- managing-network
|
- managing-network
|
||||||
|
ports:
|
||||||
managing.api.workers:
|
- "11111:11111" # Orleans silo port
|
||||||
image: ${DOCKER_REGISTRY-}managingapiworkers
|
- "30000:30000" # Orleans gateway port
|
||||||
build:
|
environment:
|
||||||
context: ../.
|
- ASPNETCORE_ENVIRONMENT=Production
|
||||||
dockerfile: Managing.Api.Workers/Dockerfile
|
- RUN_ORLEANS_GRAINS=true
|
||||||
networks:
|
hostname: managing-api
|
||||||
- managing-network
|
|
||||||
|
|
||||||
influxdb:
|
influxdb:
|
||||||
image: influxdb:latest
|
image: influxdb:latest
|
||||||
@@ -33,10 +27,10 @@ services:
|
|||||||
- managing-network
|
- managing-network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mongodata: {}
|
|
||||||
influxdata: {}
|
influxdata: {}
|
||||||
postgresdata: {}
|
postgresdata: {}
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
managing-network:
|
managing-network:
|
||||||
name: managing-network
|
name: managing-network
|
||||||
|
driver: bridge
|
||||||
@@ -8,4 +8,5 @@ public interface IInfluxDbRepository
|
|||||||
|
|
||||||
Task<T> QueryAsync<T>(Func<QueryApi, Task<T>> action);
|
Task<T> QueryAsync<T>(Func<QueryApi, Task<T>> action);
|
||||||
void Write(Action<WriteApi> action);
|
void Write(Action<WriteApi> action);
|
||||||
|
Task WriteAsync(Func<WriteApi, Task> action);
|
||||||
}
|
}
|
||||||
@@ -111,9 +111,9 @@ public class CandleRepository : ICandleRepository
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InsertCandle(Candle candle)
|
public async Task InsertCandle(Candle candle)
|
||||||
{
|
{
|
||||||
_influxDbRepository.Write(write =>
|
await _influxDbRepository.WriteAsync(write =>
|
||||||
{
|
{
|
||||||
PriceDto price = PriceHelpers.Map(candle);
|
PriceDto price = PriceHelpers.Map(candle);
|
||||||
write.WriteMeasurement(
|
write.WriteMeasurement(
|
||||||
@@ -121,6 +121,7 @@ public class CandleRepository : ICandleRepository
|
|||||||
WritePrecision.Ns,
|
WritePrecision.Ns,
|
||||||
_priceBucket,
|
_priceBucket,
|
||||||
_influxDbRepository.Organization);
|
_influxDbRepository.Organization);
|
||||||
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ public class InfluxDbRepository : IInfluxDbRepository
|
|||||||
action(write);
|
action(write);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task WriteAsync(Func<WriteApi, Task> action)
|
||||||
|
{
|
||||||
|
// Get write API asynchronously
|
||||||
|
using var client = new InfluxDBClient(_url, _token);
|
||||||
|
using var write = client.GetWriteApi();
|
||||||
|
return action(write);
|
||||||
|
}
|
||||||
public async Task<T> QueryAsync<T>(Func<QueryApi, Task<T>> action)
|
public async Task<T> QueryAsync<T>(Func<QueryApi, Task<T>> action)
|
||||||
{
|
{
|
||||||
using var client = InfluxDBClientFactory.Create(_url, _token);
|
using var client = InfluxDBClientFactory.Create(_url, _token);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {type Address, zeroAddress} from "viem";
|
import {type Address, zeroAddress} from "viem";
|
||||||
import {ARBITRUM, AVALANCHE, AVALANCHE_FUJI} from "./chains.js";
|
import {ARBITRUM, AVALANCHE, AVALANCHE_FUJI} from "./chains.js";
|
||||||
|
|
||||||
|
|
||||||
export const CONTRACTS = {
|
export const CONTRACTS = {
|
||||||
[ARBITRUM]: {
|
[ARBITRUM]: {
|
||||||
// arbitrum mainnet
|
// arbitrum mainnet
|
||||||
@@ -50,29 +51,23 @@ export const CONTRACTS = {
|
|||||||
DataStore: "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8",
|
DataStore: "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8",
|
||||||
EventEmitter: "0xC8ee91A54287DB53897056e12D9819156D3822Fb",
|
EventEmitter: "0xC8ee91A54287DB53897056e12D9819156D3822Fb",
|
||||||
SubaccountRouter: "0xa329221a77BE08485f59310b873b14815c82E10D",
|
SubaccountRouter: "0xa329221a77BE08485f59310b873b14815c82E10D",
|
||||||
ExchangeRouter: "0x602b805EedddBbD9ddff44A7dcBD46cb07849685",
|
ExchangeRouter: "0x5ac4e27341e4cccb3e5fd62f9e62db2adf43dd57",
|
||||||
DepositVault: "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55",
|
DepositVault: "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55",
|
||||||
WithdrawalVault: "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55",
|
WithdrawalVault: "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55",
|
||||||
OrderVault: "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5",
|
OrderVault: "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5",
|
||||||
ShiftVault: "0xfe99609C4AA83ff6816b64563Bdffd7fa68753Ab",
|
ShiftVault: "0xfe99609C4AA83ff6816b64563Bdffd7fa68753Ab",
|
||||||
SyntheticsReader: "0xcF2845Ab3866842A6b51Fb6a551b92dF58333574",
|
SyntheticsReader: "0x0537C767cDAC0726c76Bb89e92904fe28fd02fE1",
|
||||||
SyntheticsRouter: "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6",
|
SyntheticsRouter: "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6",
|
||||||
|
|
||||||
GlvReader: "0x6a9505D0B44cFA863d9281EA5B0b34cB36243b45",
|
GlvReader: "0x6a9505D0B44cFA863d9281EA5B0b34cB36243b45",
|
||||||
GlvRouter: "0x994c598e3b0661bb805d53c6fa6b4504b23b68dd",
|
GlvRouter: "0x994c598e3b0661bb805d53c6fa6b4504b23b68dd",
|
||||||
GlvVault: "0x393053B58f9678C9c28c2cE941fF6cac49C3F8f9",
|
GlvVault: "0x393053B58f9678C9c28c2cE941fF6cac49C3F8f9",
|
||||||
|
|
||||||
GelatoRelayRouter: "0x9EB239eDf4c6f4c4fC9d30ea2017F8716d049C8D",
|
|
||||||
SubaccountGelatoRelayRouter: "0x5F345B765d5856bC0843cEE8bE234b575eC77DBC",
|
|
||||||
|
|
||||||
ExternalHandler: "0x389CEf541397e872dC04421f166B5Bc2E0b374a5",
|
ExternalHandler: "0x389CEf541397e872dC04421f166B5Bc2E0b374a5",
|
||||||
|
|
||||||
OpenOceanRouter: "0x6352a56caadC4F1E25CD6c75970Fa768A3304e64",
|
OpenOceanRouter: "0x6352a56caadC4F1E25CD6c75970Fa768A3304e64",
|
||||||
|
|
||||||
ChainlinkPriceFeedProvider: "0x527FB0bCfF63C47761039bB386cFE181A92a4701",
|
|
||||||
|
|
||||||
Multicall: "0x842ec2c7d803033edf55e478f461fc547bc54eb2",
|
Multicall: "0x842ec2c7d803033edf55e478f461fc547bc54eb2",
|
||||||
ArbitrumNodeInterface: "0x00000000000000000000000000000000000000C8",
|
|
||||||
ClaimHandler: "0xCF2b097517EEBD6c36756A82844D2ec21Ee4C025",
|
|
||||||
},
|
},
|
||||||
[AVALANCHE]: {
|
[AVALANCHE]: {
|
||||||
// avalanche
|
// avalanche
|
||||||
@@ -123,29 +118,23 @@ export const CONTRACTS = {
|
|||||||
DataStore: "0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6",
|
DataStore: "0x2F0b22339414ADeD7D5F06f9D604c7fF5b2fe3f6",
|
||||||
EventEmitter: "0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26",
|
EventEmitter: "0xDb17B211c34240B014ab6d61d4A31FA0C0e20c26",
|
||||||
SubaccountRouter: "0x5aEb6AD978f59e220aA9099e09574e1c5E03AafD",
|
SubaccountRouter: "0x5aEb6AD978f59e220aA9099e09574e1c5E03AafD",
|
||||||
ExchangeRouter: "0xFa843af557824Be5127eaCB3c4B5D86EADEB73A1",
|
ExchangeRouter: "0xe37d052e1deb99901de205e7186e31a36e4ef70c",
|
||||||
DepositVault: "0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF",
|
DepositVault: "0x90c670825d0C62ede1c5ee9571d6d9a17A722DFF",
|
||||||
WithdrawalVault: "0xf5F30B10141E1F63FC11eD772931A8294a591996",
|
WithdrawalVault: "0xf5F30B10141E1F63FC11eD772931A8294a591996",
|
||||||
OrderVault: "0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C",
|
OrderVault: "0xD3D60D22d415aD43b7e64b510D86A30f19B1B12C",
|
||||||
ShiftVault: "0x7fC46CCb386e9bbBFB49A2639002734C3Ec52b39",
|
ShiftVault: "0x7fC46CCb386e9bbBFB49A2639002734C3Ec52b39",
|
||||||
SyntheticsReader: "0xc304F8e9872A9c00371A7406662dC10A10740AA8",
|
SyntheticsReader: "0x618fCEe30D9A26e8533C3B244CAd2D6486AFf655",
|
||||||
SyntheticsRouter: "0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68",
|
SyntheticsRouter: "0x820F5FfC5b525cD4d88Cd91aCf2c28F16530Cc68",
|
||||||
|
|
||||||
GlvReader: "0xae9596a1C438675AcC75f69d32E21Ac9c8fF99bD",
|
GlvReader: "0xae9596a1C438675AcC75f69d32E21Ac9c8fF99bD",
|
||||||
GlvRouter: "0x16500c1d8ffe2f695d8dcadf753f664993287ae4",
|
GlvRouter: "0x16500c1d8ffe2f695d8dcadf753f664993287ae4",
|
||||||
GlvVault: "0x527FB0bCfF63C47761039bB386cFE181A92a4701",
|
GlvVault: "0x527FB0bCfF63C47761039bB386cFE181A92a4701",
|
||||||
|
|
||||||
GelatoRelayRouter: "0x035A9A047d20a486e14A613B04d5a95d7A617c5D",
|
|
||||||
SubaccountGelatoRelayRouter: "0x3B753c0D0aE55530f24532B8Bb9d0bAcD5B675C0",
|
|
||||||
|
|
||||||
ExternalHandler: "0xD149573a098223a9185433290a5A5CDbFa54a8A9",
|
|
||||||
OpenOceanRouter: "0x6352a56caadC4F1E25CD6c75970Fa768A3304e64",
|
OpenOceanRouter: "0x6352a56caadC4F1E25CD6c75970Fa768A3304e64",
|
||||||
|
|
||||||
ChainlinkPriceFeedProvider: "0x713c6a2479f6C079055A6AD3690D95dEDCEf9e1e",
|
ExternalHandler: "0xD149573a098223a9185433290a5A5CDbFa54a8A9",
|
||||||
|
|
||||||
Multicall: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
Multicall: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
||||||
ArbitrumNodeInterface: zeroAddress,
|
|
||||||
ClaimHandler: "0xF73CE08A22c67f19d75892457817e917cB3f9493",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[AVALANCHE_FUJI]: {
|
[AVALANCHE_FUJI]: {
|
||||||
@@ -199,7 +188,7 @@ export const CONTRACTS = {
|
|||||||
WithdrawalVault: "0x74d49B6A630Bf519bDb6E4efc4354C420418A6A2",
|
WithdrawalVault: "0x74d49B6A630Bf519bDb6E4efc4354C420418A6A2",
|
||||||
OrderVault: "0x25D23e8E655727F2687CC808BB9589525A6F599B",
|
OrderVault: "0x25D23e8E655727F2687CC808BB9589525A6F599B",
|
||||||
ShiftVault: "0x257D0EA0B040E2Cd1D456fB4C66d7814102aD346",
|
ShiftVault: "0x257D0EA0B040E2Cd1D456fB4C66d7814102aD346",
|
||||||
SyntheticsReader: "0xA71e8b30c9414852F065e4cE12bbCC05cF50937A",
|
SyntheticsReader: "0x16Fb5b8846fbfAe09c034fCdF3D3F9492484DA80",
|
||||||
SyntheticsRouter: "0x5e7d61e4C52123ADF651961e4833aCc349b61491",
|
SyntheticsRouter: "0x5e7d61e4C52123ADF651961e4833aCc349b61491",
|
||||||
Timelock: zeroAddress,
|
Timelock: zeroAddress,
|
||||||
|
|
||||||
@@ -207,17 +196,11 @@ export const CONTRACTS = {
|
|||||||
GlvRouter: "0x377d979AB35Cd848497707ffa6Ee91783f925b80",
|
GlvRouter: "0x377d979AB35Cd848497707ffa6Ee91783f925b80",
|
||||||
GlvVault: "0x76f93b5240DF811a3fc32bEDd58daA5784e46C96",
|
GlvVault: "0x76f93b5240DF811a3fc32bEDd58daA5784e46C96",
|
||||||
|
|
||||||
GelatoRelayRouter: zeroAddress,
|
|
||||||
SubaccountGelatoRelayRouter: zeroAddress,
|
|
||||||
|
|
||||||
OpenOceanRouter: zeroAddress,
|
OpenOceanRouter: zeroAddress,
|
||||||
|
|
||||||
ExternalHandler: "0x0d9F90c66C392c4d0e70EE0d399c43729B942512",
|
ExternalHandler: "0x0d9F90c66C392c4d0e70EE0d399c43729B942512",
|
||||||
|
|
||||||
ChainlinkPriceFeedProvider: zeroAddress,
|
|
||||||
|
|
||||||
Multicall: "0x0f53e512b49202a37c81c6085417C9a9005F2196",
|
Multicall: "0x0f53e512b49202a37c81c6085417C9a9005F2196",
|
||||||
ArbitrumNodeInterface: zeroAddress,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,5 @@ export { default as CustomScenario } from './CustomScenario/CustomScenario'
|
|||||||
export { default as SpotLightBadge } from './SpotLightBadge/SpotLightBadge'
|
export { default as SpotLightBadge } from './SpotLightBadge/SpotLightBadge'
|
||||||
export { default as StatusBadge } from './StatusBadge/StatusBadge'
|
export { default as StatusBadge } from './StatusBadge/StatusBadge'
|
||||||
export { default as PositionsList } from './Positions/PositionList'
|
export { default as PositionsList } from './Positions/PositionList'
|
||||||
export { default as WorkflowCanvas } from './Workflow/workflowCanvas'
|
|
||||||
export { default as ScenarioModal } from './ScenarioModal'
|
export { default as ScenarioModal } from './ScenarioModal'
|
||||||
export { default as BotNameModal } from './BotNameModal/BotNameModal'
|
export { default as BotNameModal } from './BotNameModal/BotNameModal'
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application.Tests"
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Messengers", "Managing.Infrastructure.Messengers\Managing.Infrastructure.Messengers.csproj", "{AD40302A-27C7-4E9D-B644-C7B141571EAF}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Messengers", "Managing.Infrastructure.Messengers\Managing.Infrastructure.Messengers.csproj", "{AD40302A-27C7-4E9D-B644-C7B141571EAF}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Api.Workers", "Managing.Api.Workers\Managing.Api.Workers.csproj", "{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Databases", "Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj", "{E6CB238E-8F60-4139-BDE6-31534832198E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Infrastructure.Databases", "Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj", "{E6CB238E-8F60-4139-BDE6-31534832198E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application.Abstractions", "Managing.Application.Abstractions\Managing.Application.Abstractions.csproj", "{283AC491-97C3-49E0-AB17-272EFB4E5A9C}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Managing.Application.Abstractions", "Managing.Application.Abstractions\Managing.Application.Abstractions.csproj", "{283AC491-97C3-49E0-AB17-272EFB4E5A9C}"
|
||||||
@@ -166,14 +164,6 @@ Global
|
|||||||
{AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|Any CPU.Build.0 = Release|Any CPU
|
{AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|x64.ActiveCfg = Release|Any CPU
|
{AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|x64.Build.0 = Release|Any CPU
|
{AD40302A-27C7-4E9D-B644-C7B141571EAF}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E}.Release|x64.Build.0 = Release|x64
|
|
||||||
{E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{E6CB238E-8F60-4139-BDE6-31534832198E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
@@ -262,7 +252,6 @@ Global
|
|||||||
{837B12AD-E96C-40CE-9DEE-931442A6C15E} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
{837B12AD-E96C-40CE-9DEE-931442A6C15E} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
||||||
{35A05E76-29F6-4DC1-886D-FD69926CB490} = {8F2ECEA7-5BCA-45DF-B6E3-88AADD7AFD45}
|
{35A05E76-29F6-4DC1-886D-FD69926CB490} = {8F2ECEA7-5BCA-45DF-B6E3-88AADD7AFD45}
|
||||||
{AD40302A-27C7-4E9D-B644-C7B141571EAF} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
{AD40302A-27C7-4E9D-B644-C7B141571EAF} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
||||||
{0DC797C2-007C-496E-B4C9-FDBD29D4EF4E} = {A1296069-2816-43D4-882C-516BCB718D03}
|
|
||||||
{E6CB238E-8F60-4139-BDE6-31534832198E} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
{E6CB238E-8F60-4139-BDE6-31534832198E} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
||||||
{283AC491-97C3-49E0-AB17-272EFB4E5A9C} = {F6774DB0-DF13-4077-BC94-0E67EE105C4C}
|
{283AC491-97C3-49E0-AB17-272EFB4E5A9C} = {F6774DB0-DF13-4077-BC94-0E67EE105C4C}
|
||||||
{CDDF92D4-9D2E-4134-BD44-3064D6EF462D} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
{CDDF92D4-9D2E-4134-BD44-3064D6EF462D} = {E453D33B-5C2B-4AA1-834D-2C916EC95FC6}
|
||||||
|
|||||||
Reference in New Issue
Block a user