Fix autoswap

This commit is contained in:
2025-09-22 00:22:39 +07:00
parent 6aad2834a9
commit 7c3c0f38ec
5 changed files with 68 additions and 59 deletions

View File

@@ -58,7 +58,7 @@ public class AgentGrain : Grain, IAgentGrain
{ {
_state.State.AgentName = agentName; _state.State.AgentName = agentName;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
// Create an empty AgentSummary for the new agent // Create an empty AgentSummary for the new agent
var emptySummary = new AgentSummary var emptySummary = new AgentSummary
{ {
@@ -75,7 +75,7 @@ public class AgentGrain : Grain, IAgentGrain
TotalVolume = 0, TotalVolume = 0,
TotalBalance = 0 TotalBalance = 0
}; };
await _agentService.SaveOrUpdateAgentSummary(emptySummary); await _agentService.SaveOrUpdateAgentSummary(emptySummary);
_logger.LogInformation("Agent {UserId} initialized with name {AgentName} and empty summary", userId, agentName); _logger.LogInformation("Agent {UserId} initialized with name {AgentName} and empty summary", userId, agentName);
} }
@@ -93,7 +93,7 @@ public class AgentGrain : Grain, IAgentGrain
{ {
_logger.LogInformation("Received agent summary update event for user {UserId}, event type: {EventType}", _logger.LogInformation("Received agent summary update event for user {UserId}, event type: {EventType}",
this.GetPrimaryKeyLong(), updateEvent.EventType); this.GetPrimaryKeyLong(), updateEvent.EventType);
// Only update summary if the event is for this agent's bots // Only update summary if the event is for this agent's bots
if (_state.State.BotIds.Contains(updateEvent.BotId)) if (_state.State.BotIds.Contains(updateEvent.BotId))
{ {
@@ -217,7 +217,7 @@ public class AgentGrain : Grain, IAgentGrain
{ {
await _state.WriteStateAsync(); await _state.WriteStateAsync();
_logger.LogInformation("Bot {BotId} registered to Agent {UserId}", botId, this.GetPrimaryKeyLong()); _logger.LogInformation("Bot {BotId} registered to Agent {UserId}", botId, this.GetPrimaryKeyLong());
// Update summary after registering bot // Update summary after registering bot
await UpdateSummary(); await UpdateSummary();
} }
@@ -229,7 +229,7 @@ public class AgentGrain : Grain, IAgentGrain
{ {
await _state.WriteStateAsync(); await _state.WriteStateAsync();
_logger.LogInformation("Bot {BotId} unregistered from Agent {UserId}", botId, this.GetPrimaryKeyLong()); _logger.LogInformation("Bot {BotId} unregistered from Agent {UserId}", botId, this.GetPrimaryKeyLong());
// Update summary after unregistering bot // Update summary after unregistering bot
await UpdateSummary(); await UpdateSummary();
} }
@@ -242,7 +242,7 @@ public class AgentGrain : Grain, IAgentGrain
// Check if a swap is already in progress // Check if a swap is already in progress
if (_state.State.IsSwapInProgress) if (_state.State.IsSwapInProgress)
{ {
_logger.LogInformation("Swap already in progress for agent {UserId}, bot {RequestingBotId} will wait", _logger.LogInformation("Swap already in progress for agent {UserId}, bot {RequestingBotId} will wait",
this.GetPrimaryKeyLong(), requestingBotId); this.GetPrimaryKeyLong(), requestingBotId);
return new BalanceCheckResult return new BalanceCheckResult
{ {
@@ -254,10 +254,11 @@ public class AgentGrain : Grain, IAgentGrain
} }
// Check cooldown period (5 minutes between swaps) // Check cooldown period (5 minutes between swaps)
if (_state.State.LastSwapTime.HasValue && if (_state.State.LastSwapTime.HasValue &&
DateTime.UtcNow - _state.State.LastSwapTime.Value < TimeSpan.FromMinutes(5)) DateTime.UtcNow - _state.State.LastSwapTime.Value < TimeSpan.FromMinutes(5))
{ {
_logger.LogInformation("Swap cooldown period active for agent {UserId}, bot {RequestingBotId} will wait", _logger.LogInformation(
"Swap cooldown period active for agent {UserId}, bot {RequestingBotId} will wait",
this.GetPrimaryKeyLong(), requestingBotId); this.GetPrimaryKeyLong(), requestingBotId);
return new BalanceCheckResult return new BalanceCheckResult
{ {
@@ -272,7 +273,7 @@ public class AgentGrain : Grain, IAgentGrain
var balanceData = await GetOrRefreshBalanceDataAsync(accountName); var balanceData = await GetOrRefreshBalanceDataAsync(accountName);
if (balanceData == null) if (balanceData == null)
{ {
_logger.LogError("Failed to get balance data for account {AccountName}, user {UserId}", _logger.LogError("Failed to get balance data for account {AccountName}, user {UserId}",
accountName, this.GetPrimaryKeyLong()); accountName, this.GetPrimaryKeyLong());
return new BalanceCheckResult return new BalanceCheckResult
{ {
@@ -283,20 +284,23 @@ public class AgentGrain : Grain, IAgentGrain
}; };
} }
_logger.LogInformation("Agent {UserId} balance check - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (cached: {IsCached})", _logger.LogInformation(
this.GetPrimaryKeyLong(), balanceData.EthValueInUsd, balanceData.UsdcValue, "Agent {UserId} balance check - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (cached: {IsCached})",
this.GetPrimaryKeyLong(), balanceData.EthValueInUsd, balanceData.UsdcValue,
_state.State.CachedBalanceData?.IsValid == true); _state.State.CachedBalanceData?.IsValid == true);
// Check USDC minimum balance first (this will stop the bot if insufficient) // Check USDC minimum balance first (this will stop the bot if insufficient)
if (balanceData.UsdcValue < Constants.GMX.Config.MinimumPositionAmount) if (balanceData.UsdcValue < Constants.GMX.Config.MinimumPositionAmount)
{ {
_logger.LogWarning("USDC balance is below minimum required amount - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (minimum: {Minimum})", _logger.LogWarning(
"USDC balance is below minimum required amount - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (minimum: {Minimum})",
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.MinimumPositionAmount); balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.MinimumPositionAmount);
return new BalanceCheckResult return new BalanceCheckResult
{ {
IsSuccessful = false, IsSuccessful = false,
FailureReason = BalanceCheckFailureReason.InsufficientUsdcBelowMinimum, FailureReason = BalanceCheckFailureReason.InsufficientUsdcBelowMinimum,
Message = $"USDC balance below minimum required amount ({Constants.GMX.Config.MinimumPositionAmount} USD)", Message =
$"USDC balance below minimum required amount ({Constants.GMX.Config.MinimumPositionAmount} USD)",
ShouldStopBot = true ShouldStopBot = true
}; };
} }
@@ -314,9 +318,11 @@ public class AgentGrain : Grain, IAgentGrain
} }
// Check if we have enough USDC for swap (need at least 5 USD for swap) // Check if we have enough USDC for swap (need at least 5 USD for swap)
if (balanceData.UsdcValue < (Constants.GMX.Config.MinimumPositionAmount + Constants.GMX.Config.AutoSwapAmount) ) if (balanceData.UsdcValue <
(Constants.GMX.Config.MinimumPositionAmount + (decimal)Constants.GMX.Config.AutoSwapAmount))
{ {
_logger.LogWarning("Insufficient USDC balance for swap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (need {AutoSwapAmount} USD for swap)", _logger.LogWarning(
"Insufficient USDC balance for swap - ETH: {EthValue:F2} USD, USDC: {UsdcValue:F2} USD (need {AutoSwapAmount} USD for swap)",
balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.AutoSwapAmount); balanceData.EthValueInUsd, balanceData.UsdcValue, Constants.GMX.Config.AutoSwapAmount);
return new BalanceCheckResult return new BalanceCheckResult
{ {
@@ -333,7 +339,8 @@ public class AgentGrain : Grain, IAgentGrain
try try
{ {
_logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping 5 USDC", this.GetPrimaryKeyLong()); _logger.LogInformation("Initiating USDC to ETH swap for agent {UserId} - swapping 5 USDC",
this.GetPrimaryKeyLong());
// Get user for the swap // Get user for the swap
var userId = (int)this.GetPrimaryKeyLong(); var userId = (int)this.GetPrimaryKeyLong();
@@ -351,14 +358,15 @@ public class AgentGrain : Grain, IAgentGrain
} }
// Perform the swap // Perform the swap
var swapInfo = await _accountService.SwapGmxTokensAsync(user, accountName, var swapInfo = await _accountService.SwapGmxTokensAsync(user, accountName,
Ticker.USDC, Ticker.ETH, 5); Ticker.USDC, Ticker.ETH, Constants.GMX.Config.AutoSwapAmount);
if (swapInfo.Success) if (swapInfo.Success)
{ {
_logger.LogInformation("Successfully swapped 5 USDC to ETH for agent {UserId}, transaction hash: {Hash}", _logger.LogInformation(
"Successfully swapped 5 USDC to ETH for agent {UserId}, transaction hash: {Hash}",
userId, swapInfo.Hash); userId, swapInfo.Hash);
// Update last swap time and invalidate cache // Update last swap time and invalidate cache
_state.State.LastSwapTime = DateTime.UtcNow; _state.State.LastSwapTime = DateTime.UtcNow;
_state.State.CachedBalanceData = null; // Invalidate cache after successful swap _state.State.CachedBalanceData = null; // Invalidate cache after successful swap
@@ -372,7 +380,7 @@ public class AgentGrain : Grain, IAgentGrain
} }
else else
{ {
_logger.LogError("Failed to swap USDC to ETH for agent {UserId}: {Error}", _logger.LogError("Failed to swap USDC to ETH for agent {UserId}: {Error}",
userId, swapInfo.Error ?? swapInfo.Message); userId, swapInfo.Error ?? swapInfo.Message);
return new BalanceCheckResult return new BalanceCheckResult
{ {
@@ -392,13 +400,13 @@ public class AgentGrain : Grain, IAgentGrain
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error checking/ensuring ETH balance for agent {UserId}, bot {RequestingBotId}", _logger.LogError(ex, "Error checking/ensuring ETH balance for agent {UserId}, bot {RequestingBotId}",
this.GetPrimaryKeyLong(), requestingBotId); this.GetPrimaryKeyLong(), requestingBotId);
// Clear swap in progress flag on error // Clear swap in progress flag on error
_state.State.IsSwapInProgress = false; _state.State.IsSwapInProgress = false;
await _state.WriteStateAsync(); await _state.WriteStateAsync();
return new BalanceCheckResult return new BalanceCheckResult
{ {
IsSuccessful = false, IsSuccessful = false,
@@ -417,7 +425,7 @@ public class AgentGrain : Grain, IAgentGrain
try try
{ {
// Check if we have valid cached data for the same account // Check if we have valid cached data for the same account
if (_state.State.CachedBalanceData?.IsValid == true && if (_state.State.CachedBalanceData?.IsValid == true &&
_state.State.CachedBalanceData.AccountName == accountName) _state.State.CachedBalanceData.AccountName == accountName)
{ {
_logger.LogDebug("Using cached balance data for account {AccountName}", accountName); _logger.LogDebug("Using cached balance data for account {AccountName}", accountName);
@@ -426,7 +434,7 @@ public class AgentGrain : Grain, IAgentGrain
// Fetch fresh balance data // Fetch fresh balance data
_logger.LogInformation("Fetching fresh balance data for account {AccountName}", accountName); _logger.LogInformation("Fetching fresh balance data for account {AccountName}", accountName);
var userId = (int)this.GetPrimaryKeyLong(); var userId = (int)this.GetPrimaryKeyLong();
var user = await _userService.GetUserByIdAsync(userId); var user = await _userService.GetUserByIdAsync(userId);
if (user == null) if (user == null)
@@ -467,7 +475,7 @@ public class AgentGrain : Grain, IAgentGrain
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error fetching balance data for account {AccountName}, user {UserId}", _logger.LogError(ex, "Error fetching balance data for account {AccountName}, user {UserId}",
accountName, this.GetPrimaryKeyLong()); accountName, this.GetPrimaryKeyLong());
return null; return null;
} }

View File

@@ -82,25 +82,25 @@ namespace Managing.Common
Ticker.WIF, Ticker.WIF,
}; };
public static readonly Ticker[] SupportedTickers = public static readonly Ticker[] SupportedTickers =
{ {
Ticker.BTC, Ticker.BTC,
Ticker.ETH, Ticker.ETH,
Ticker.BNB, Ticker.BNB,
Ticker.DOGE, Ticker.DOGE,
Ticker.ADA, Ticker.ADA,
Ticker.SOL, Ticker.SOL,
Ticker.XRP, Ticker.XRP,
Ticker.LINK, Ticker.LINK,
Ticker.RENDER, Ticker.RENDER,
Ticker.SUI, Ticker.SUI,
Ticker.GMX, Ticker.GMX,
Ticker.ARB, Ticker.ARB,
Ticker.PEPE, Ticker.PEPE,
Ticker.PENDLE, Ticker.PENDLE,
Ticker.AAVE, Ticker.AAVE,
Ticker.HYPE Ticker.HYPE
}; };
public static class Decimals public static class Decimals
{ {
@@ -109,7 +109,7 @@ namespace Managing.Common
public const decimal MinimumPositionAmount = 5m; public const decimal MinimumPositionAmount = 5m;
public const decimal MinimumEthBalance = 2m; public const decimal MinimumEthBalance = 2m;
public const decimal AutoSwapAmount = 3m; public const double AutoSwapAmount = 3;
} }
public class TokenAddress public class TokenAddress

View File

@@ -180,14 +180,20 @@ async function init () {
} }
} }
// Store the request ID for later use // Store the request ID for later use using a symbol to avoid TypeScript property errors
request.idempotencyKey = requestId as string const IDEMPOTENCY_KEY_SYMBOL = Symbol.for('idempotency-key')
// Attach the symbol to the request object
;(request as any)[IDEMPOTENCY_KEY_SYMBOL] = requestId as string
}) })
// Add post-handler hook to store successful responses // Add post-handler hook to store successful responses
app.addHook('onSend', async (request, reply, payload) => { app.addHook('onSend', async (request, reply, payload) => {
if (request.idempotencyKey && request.method === 'POST') { // Retrieve the idempotency key from the symbol
const requestId = request.idempotencyKey const IDEMPOTENCY_KEY_SYMBOL = Symbol.for('idempotency-key')
const idempotencyKey = (request as any)[IDEMPOTENCY_KEY_SYMBOL]
if (idempotencyKey && request.method === 'POST') {
const requestId = idempotencyKey
// Only store successful responses (2xx status codes) // Only store successful responses (2xx status codes)
if (reply.statusCode >= 200 && reply.statusCode < 300) { if (reply.statusCode >= 200 && reply.statusCode < 300) {

View File

@@ -1,5 +0,0 @@
declare module 'fastify' {
interface FastifyRequest {
idempotencyKey?: string
}
}

View File

@@ -7,15 +7,15 @@ describe('swap tokens implementation', () => {
it('should swap SOL to USDC successfully', async () => { it('should swap SOL to USDC successfully', async () => {
try { try {
const testAccount = '0xbBA4eaA534cbD0EcAed5E2fD6036Aec2E7eE309f' const testAccount = '0x932167388dD9aad41149b3cA23eBD489E2E2DD78'
const sdk = await getClientForAddress(testAccount) const sdk = await getClientForAddress(testAccount)
console.log('Account', sdk.account) console.log('Account', sdk.account)
const result = await swapGmxTokensImpl( const result = await swapGmxTokensImpl(
sdk, sdk,
Ticker.PENDLE, Ticker.ETH,
Ticker.USDC, Ticker.USDC,
13.339559522 0.0042
) )
assert.strictEqual(typeof result, 'string') assert.strictEqual(typeof result, 'string')