Add kaigen debit credit for backtest
This commit is contained in:
64
docs/KaigenConfiguration.md
Normal file
64
docs/KaigenConfiguration.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Kaigen Service Configuration
|
||||||
|
|
||||||
|
The Kaigen service is used for managing user credits during backtest operations. It requires proper configuration to function correctly.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Required Environment Variable
|
||||||
|
|
||||||
|
- **`KAIGEN_PRIVATE_KEY`**: The private key used for signing API requests to the Kaigen service.
|
||||||
|
|
||||||
|
### Setting the Environment Variable
|
||||||
|
|
||||||
|
#### Development
|
||||||
|
```bash
|
||||||
|
export KAIGEN_PRIVATE_KEY="your-private-key-here"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Production
|
||||||
|
Set the environment variable in your deployment configuration:
|
||||||
|
```bash
|
||||||
|
KAIGEN_PRIVATE_KEY=your-private-key-here
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker
|
||||||
|
```bash
|
||||||
|
docker run -e KAIGEN_PRIVATE_KEY=your-private-key-here your-app
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Compose
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- KAIGEN_PRIVATE_KEY=your-private-key-here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Structure
|
||||||
|
|
||||||
|
The Kaigen service configuration is defined in `appsettings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Kaigen": {
|
||||||
|
"BaseUrl": "https://api.kaigen.managing.live",
|
||||||
|
"DebitEndpoint": "/api/credits/debit",
|
||||||
|
"RefundEndpoint": "/api/credits/refund",
|
||||||
|
"PrivateKey": "${KAIGEN_PRIVATE_KEY}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
- **PUT** `/api/credits/debit` - Debit credits from user account
|
||||||
|
- **PUT** `/api/credits/refund` - Refund credits to user account
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- The private key should never be committed to source control
|
||||||
|
- Use environment variables or secure configuration management systems
|
||||||
|
- The private key is used for signing API requests to ensure authenticity
|
||||||
|
- Rotate the private key regularly for enhanced security
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
If the `KAIGEN_PRIVATE_KEY` environment variable is not set, the application will throw an `InvalidOperationException` with a clear error message during startup.
|
||||||
@@ -32,5 +32,11 @@
|
|||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"WorkerBotManager": true,
|
"WorkerBotManager": true,
|
||||||
"WorkerBalancesTracking": true
|
"WorkerBalancesTracking": true,
|
||||||
|
"Kaigen": {
|
||||||
|
"BaseUrl": "https://api.kaigen.managing.live",
|
||||||
|
"DebitEndpoint": "/api/credits/debit",
|
||||||
|
"RefundEndpoint": "/api/credits/refund",
|
||||||
|
"PrivateKey": "${KAIGEN_PRIVATE_KEY}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,12 @@
|
|||||||
"Web3Proxy": {
|
"Web3Proxy": {
|
||||||
"BaseUrl": "http://localhost:4111"
|
"BaseUrl": "http://localhost:4111"
|
||||||
},
|
},
|
||||||
|
"Kaigen": {
|
||||||
|
"BaseUrl": "https://api.kaigen.managing.live",
|
||||||
|
"DebitEndpoint": "/api/credits/debit",
|
||||||
|
"RefundEndpoint": "/api/credits/refund",
|
||||||
|
"PrivateKey": "${KAIGEN_PRIVATE_KEY}"
|
||||||
|
},
|
||||||
"N8n": {
|
"N8n": {
|
||||||
"WebhookUrl": "https://n8n.kai.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951"
|
"WebhookUrl": "https://n8n.kai.managing.live/webhook/fa9308b6-983b-42ec-b085-71599d655951"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Managing.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IKaigenService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Debits user credits for a backtest operation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userName">The username to debit</param>
|
||||||
|
/// <param name="debitAmount">The amount to debit</param>
|
||||||
|
/// <returns>The request ID for tracking</returns>
|
||||||
|
Task<string> DebitUserCreditsAsync(string userName, decimal debitAmount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refunds user credits if debit operation fails
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestId">The original request ID from debit operation</param>
|
||||||
|
/// <param name="userName">The username to refund</param>
|
||||||
|
/// <returns>True if refund was successful</returns>
|
||||||
|
Task<bool> RefundUserCreditsAsync(string requestId, string userName);
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ namespace Managing.Application.Backtesting
|
|||||||
private readonly IScenarioService _scenarioService;
|
private readonly IScenarioService _scenarioService;
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
|
private readonly IKaigenService _kaigenService;
|
||||||
|
|
||||||
public Backtester(
|
public Backtester(
|
||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
@@ -35,7 +36,8 @@ namespace Managing.Application.Backtesting
|
|||||||
ILogger<Backtester> logger,
|
ILogger<Backtester> logger,
|
||||||
IScenarioService scenarioService,
|
IScenarioService scenarioService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService)
|
IMessengerService messengerService,
|
||||||
|
IKaigenService kaigenService)
|
||||||
{
|
{
|
||||||
_exchangeService = exchangeService;
|
_exchangeService = exchangeService;
|
||||||
_botFactory = botFactory;
|
_botFactory = botFactory;
|
||||||
@@ -44,6 +46,7 @@ namespace Managing.Application.Backtesting
|
|||||||
_scenarioService = scenarioService;
|
_scenarioService = scenarioService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_messengerService = messengerService;
|
_messengerService = messengerService;
|
||||||
|
_kaigenService = kaigenService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
|
public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
|
||||||
@@ -82,20 +85,66 @@ namespace Managing.Application.Backtesting
|
|||||||
string requestId = null,
|
string requestId = null,
|
||||||
object metadata = null)
|
object metadata = null)
|
||||||
{
|
{
|
||||||
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
string creditRequestId = null;
|
||||||
|
|
||||||
var result = await RunBacktestWithCandles(config, candles, user, withCandles, requestId, metadata);
|
// Debit user credits before starting the backtest
|
||||||
|
if (user != null)
|
||||||
// Set start and end dates
|
|
||||||
result.StartDate = startDate;
|
|
||||||
result.EndDate = endDate;
|
|
||||||
|
|
||||||
if (save && user != null)
|
|
||||||
{
|
{
|
||||||
_backtestRepository.InsertBacktestForUser(user, result);
|
try
|
||||||
|
{
|
||||||
|
creditRequestId = await _kaigenService.DebitUserCreditsAsync(user.Name, 3);
|
||||||
|
_logger.LogInformation("Successfully debited credits for user {UserName} with request ID {RequestId}",
|
||||||
|
user.Name, creditRequestId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to debit credits for user {UserName}. Backtest will not proceed.", user.Name);
|
||||||
|
throw new Exception($"Failed to debit credits: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
try
|
||||||
|
{
|
||||||
|
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
||||||
|
|
||||||
|
var result = await RunBacktestWithCandles(config, candles, user, withCandles, requestId, metadata);
|
||||||
|
|
||||||
|
// Set start and end dates
|
||||||
|
result.StartDate = startDate;
|
||||||
|
result.EndDate = endDate;
|
||||||
|
|
||||||
|
if (save && user != null)
|
||||||
|
{
|
||||||
|
_backtestRepository.InsertBacktestForUser(user, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// If backtest fails and we debited credits, attempt to refund
|
||||||
|
if (user != null && !string.IsNullOrEmpty(creditRequestId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, user.Name);
|
||||||
|
if (refundSuccess)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Successfully refunded credits for user {UserName} after backtest failure", user.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to refund credits for user {UserName} after backtest failure", user.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception refundEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(refundEx, "Error during refund attempt for user {UserName}", user.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ public static class ApiBootstrap
|
|||||||
services.AddTransient<IWebhookService, WebhookService>();
|
services.AddTransient<IWebhookService, WebhookService>();
|
||||||
services.AddTransient<ISynthPredictionService, SynthPredictionService>();
|
services.AddTransient<ISynthPredictionService, SynthPredictionService>();
|
||||||
services.AddTransient<ISynthApiClient, SynthApiClient>();
|
services.AddTransient<ISynthApiClient, SynthApiClient>();
|
||||||
|
services.AddTransient<IKaigenService, KaigenService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@@ -118,6 +119,8 @@ public static class ApiBootstrap
|
|||||||
services.AddSingleton<IPrivySettings>(sp =>
|
services.AddSingleton<IPrivySettings>(sp =>
|
||||||
sp.GetRequiredService<IOptions<PrivySettings>>().Value);
|
sp.GetRequiredService<IOptions<PrivySettings>>().Value);
|
||||||
|
|
||||||
|
services.Configure<KaigenSettings>(configuration.GetSection("Kaigen"));
|
||||||
|
|
||||||
// Evm
|
// Evm
|
||||||
services.AddGbcFeed();
|
services.AddGbcFeed();
|
||||||
services.AddUniswapV2();
|
services.AddUniswapV2();
|
||||||
|
|||||||
165
src/Managing.Infrastructure.Web3/Services/KaigenService.cs
Normal file
165
src/Managing.Infrastructure.Web3/Services/KaigenService.cs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Nethereum.Signer;
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Evm.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration settings for Kaigen credit management service.
|
||||||
|
/// The PrivateKey should be set via the KAIGEN_PRIVATE_KEY environment variable for security.
|
||||||
|
/// </summary>
|
||||||
|
public class KaigenSettings
|
||||||
|
{
|
||||||
|
public string BaseUrl { get; set; } = "https://api.kaigen.managing.live";
|
||||||
|
public string DebitEndpoint { get; set; } = "/api/credits/debit";
|
||||||
|
public string RefundEndpoint { get; set; } = "/api/credits/refund";
|
||||||
|
public string PrivateKey { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KaigenService : IKaigenService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly KaigenSettings _settings;
|
||||||
|
private readonly ILogger<KaigenService> _logger;
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
|
||||||
|
public KaigenService(IOptions<KaigenSettings> options, ILogger<KaigenService> logger)
|
||||||
|
{
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
_settings = options.Value;
|
||||||
|
_logger = logger;
|
||||||
|
_jsonOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate required settings
|
||||||
|
if (string.IsNullOrEmpty(_settings.PrivateKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Kaigen PrivateKey is not configured. Please set the KAIGEN_PRIVATE_KEY environment variable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_settings.BaseUrl))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Kaigen BaseUrl is not configured.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> DebitUserCreditsAsync(string userName, decimal debitAmount)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var requestId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
// Create the payload for signing
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
RequestId = requestId,
|
||||||
|
UserName = userName,
|
||||||
|
DebitAmount = debitAmount
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the message to sign (concatenate the values)
|
||||||
|
var message = $"{requestId}{userName}{debitAmount}";
|
||||||
|
|
||||||
|
// Sign the message
|
||||||
|
var signature = SignMessage(message, _settings.PrivateKey);
|
||||||
|
|
||||||
|
// Create the request payload
|
||||||
|
var requestPayload = new
|
||||||
|
{
|
||||||
|
requestId = requestId,
|
||||||
|
userName = userName,
|
||||||
|
debitAmount = debitAmount,
|
||||||
|
signature = signature
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation("Debiting {Amount} credits for user {UserName} with request ID {RequestId}",
|
||||||
|
debitAmount, userName, requestId);
|
||||||
|
|
||||||
|
var response = await _httpClient.PutAsJsonAsync(
|
||||||
|
$"{_settings.BaseUrl}{_settings.DebitEndpoint}",
|
||||||
|
requestPayload,
|
||||||
|
_jsonOptions);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
_logger.LogError("Failed to debit credits. Status: {StatusCode}, Error: {Error}",
|
||||||
|
response.StatusCode, errorContent);
|
||||||
|
throw new Exception($"Failed to debit credits: {response.StatusCode} - {errorContent}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<KaigenResponse>(_jsonOptions);
|
||||||
|
_logger.LogInformation("Successfully debited {Amount} credits for user {UserName}",
|
||||||
|
debitAmount, userName);
|
||||||
|
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error debiting credits for user {UserName}", userName);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RefundUserCreditsAsync(string requestId, string userName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create the message to sign (concatenate the values)
|
||||||
|
var message = $"{requestId}{userName}";
|
||||||
|
|
||||||
|
// Sign the message
|
||||||
|
var signature = SignMessage(message, _settings.PrivateKey);
|
||||||
|
|
||||||
|
// Create the request payload
|
||||||
|
var requestPayload = new
|
||||||
|
{
|
||||||
|
requestId = requestId,
|
||||||
|
userName = userName,
|
||||||
|
signature = signature
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation("Refunding credits for user {UserName} with request ID {RequestId}",
|
||||||
|
userName, requestId);
|
||||||
|
|
||||||
|
var response = await _httpClient.PutAsJsonAsync(
|
||||||
|
$"{_settings.BaseUrl}{_settings.RefundEndpoint}",
|
||||||
|
requestPayload,
|
||||||
|
_jsonOptions);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
_logger.LogError("Failed to refund credits. Status: {StatusCode}, Error: {Error}",
|
||||||
|
response.StatusCode, errorContent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully refunded credits for user {UserName}", userName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error refunding credits for user {UserName}", userName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SignMessage(string message, string privateKey)
|
||||||
|
{
|
||||||
|
var signer = new EthereumMessageSigner();
|
||||||
|
var signature = signer.EncodeUTF8AndSign(message, new EthECKey(privateKey));
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KaigenResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user