Add agent balance fetch from proxy

This commit is contained in:
2025-08-15 20:52:37 +07:00
parent b178f15beb
commit 289fd25dc3
9 changed files with 1622 additions and 43 deletions

View File

@@ -15,5 +15,7 @@ namespace Managing.Application.Abstractions.Services
string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5);
Task<SwapInfos> SendTokenAsync(string senderAddress, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null);
Task<List<Balance>> GetWalletBalanceAsync(string address, Ticker[] assets, string[] chains);
}
}

View File

@@ -106,7 +106,7 @@ public class AccountService : IAccountService
throw new ArgumentException($"Account '{name}' not found");
}
ManageProperties(hideSecrets, getBalance, account);
await ManagePropertiesAsync(hideSecrets, getBalance, account);
if (account.User == null && account.User != null)
{
@@ -119,7 +119,7 @@ public class AccountService : IAccountService
public async Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance)
{
var account = await _accountRepository.GetAccountByKeyAsync(key);
ManageProperties(hideSecrets, getBalance, account);
await ManagePropertiesAsync(hideSecrets, getBalance, account);
return account;
}
@@ -127,7 +127,7 @@ public class AccountService : IAccountService
public async Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance)
{
var account = await _accountRepository.GetAccountByNameAsync(name);
ManageProperties(hideSecrets, getBalance, account);
await ManagePropertiesAsync(hideSecrets, getBalance, account);
return account;
}
@@ -139,7 +139,7 @@ public class AccountService : IAccountService
if (account != null)
{
ManageProperties(hideSecrets, getBalance, account);
await ManagePropertiesAsync(hideSecrets, getBalance, account);
if (account.User != null)
{
account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
@@ -161,7 +161,7 @@ public class AccountService : IAccountService
foreach (var account in result)
{
ManageProperties(hideSecrets, getBalance, account);
await ManagePropertiesAsync(hideSecrets, getBalance, account);
accounts.Add(account);
}
@@ -190,7 +190,7 @@ public class AccountService : IAccountService
foreach (var account in result.Where(a => a.User.Name == user.Name))
{
ManageProperties(hideSecrets, getBalance, account);
await ManagePropertiesAsync(hideSecrets, getBalance, account);
accounts.Add(account);
}
@@ -333,7 +333,7 @@ public class AccountService : IAccountService
}
}
private void ManageProperties(bool hideSecrets, bool getBalance, Account account)
private async Task ManagePropertiesAsync(bool hideSecrets, bool getBalance, Account account)
{
if (account != null)
{
@@ -341,7 +341,7 @@ public class AccountService : IAccountService
{
try
{
account.Balances = _exchangeService.GetBalances(account).Result;
account.Balances = await _exchangeService.GetBalances(account);
}
catch (Exception ex)
{

View File

@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddTotalBalanceToAgent : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "TotalBalance",
table: "AgentSummaries",
type: "numeric(18,8)",
precision: 18,
scale: 8,
nullable: false,
defaultValue: 0m);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TotalBalance",
table: "AgentSummaries");
}
}
}

View File

@@ -90,6 +90,10 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime?>("Runtime")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("TotalBalance")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("TotalPnL")
.HasColumnType("decimal(18,8)");

View File

@@ -545,6 +545,7 @@ public class ManagingDbContext : DbContext
entity.Property(e => e.UpdatedAt).IsRequired();
entity.Property(e => e.ActiveStrategiesCount).IsRequired();
entity.Property(e => e.TotalVolume).HasPrecision(18, 8);
entity.Property(e => e.TotalBalance).HasPrecision(18, 8);
// Create indexes for common queries
entity.HasIndex(e => e.UserId).IsUnique();

View File

@@ -325,6 +325,49 @@ public class EvmManager : IEvmManager
public async Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress)
{
try
{
// Define the assets and chains we want to query
var assets = new[] { Ticker.USDC, Ticker.ETH };
var chains = new[] { "arbitrum", "ethereum" };
// Get balances from Web3Proxy service
var balances = await _web3ProxyService.GetWalletBalanceAsync(publicAddress, assets, chains);
// Convert Balance objects to EvmBalance objects
var evmBalances = new List<EvmBalance>();
foreach (var balance in balances)
{
if (balance.Amount > 0)
{
var evmBalance = new EvmBalance
{
Balance = balance.Amount,
TokenName = balance.TokenName ?? "Unknown",
TokenAddress = balance.TokenAdress ?? "",
TokenImage = balance.TokenImage ?? "",
Price = balance.Price,
Value = balance.Value,
Chain = new Chain
{
Id = balance.Chain?.Id ?? "",
Name = balance.Chain?.Name ?? "Unknown",
ChainId = balance.Chain?.ChainId ?? 0,
RpcUrl = balance.Chain?.RpcUrl ?? ""
}
};
evmBalances.Add(evmBalance);
}
}
return evmBalances;
}
catch (Exception ex)
{
// Log the exception and fallback to original implementation if Web3Proxy fails
Console.WriteLine($"Web3Proxy balance retrieval failed: {ex.Message}");
var chainBalances = new List<EvmBalance>();
var chains = ChainService.GetChains();
@@ -335,6 +378,7 @@ public class EvmManager : IEvmManager
return chainBalances;
}
}
public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe,
bool isFirstCall = false)

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using Managing.Domain.Accounts;
namespace Managing.Infrastructure.Evm.Models.Proxy
{
@@ -138,4 +139,16 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
[JsonPropertyName("hash")]
public string Hash { get; set; }
}
/// <summary>
/// Response for wallet balance operations
/// </summary>
public class Web3ProxyBalanceResponse : Web3ProxyResponse
{
/// <summary>
/// List of wallet balances if successful
/// </summary>
[JsonPropertyName("balances")]
public List<Balance> Balances { get; set; }
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
@@ -181,7 +182,8 @@ namespace Managing.Infrastructure.Evm.Services
};
}
public async Task<SwapInfos> SwapGmxTokensAsync(string account, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
public async Task<SwapInfos> SwapGmxTokensAsync(string account, Ticker fromTicker, Ticker toTicker,
double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5)
{
var payload = new
{
@@ -213,7 +215,8 @@ namespace Managing.Infrastructure.Evm.Services
};
}
public async Task<SwapInfos> SendTokenAsync(string senderAddress, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null)
public async Task<SwapInfos> SendTokenAsync(string senderAddress, string recipientAddress, Ticker ticker,
decimal amount, int? chainId = null)
{
var payload = new
{
@@ -243,6 +246,30 @@ namespace Managing.Infrastructure.Evm.Services
};
}
public async Task<List<Balance>> GetWalletBalanceAsync(string address, Ticker[] assets, string[] chains)
{
var payload = new
{
address,
asset = assets,
chain = chains
};
var response = await GetPrivyServiceAsync<Web3ProxyBalanceResponse>("/wallet-balance", payload);
if (response == null)
{
throw new Web3ProxyException("Wallet balance response is null");
}
if (!response.Success)
{
throw new Web3ProxyException($"Wallet balance request failed: {response.Error}");
}
return response.Balances ?? new List<Balance>();
}
private async Task HandleErrorResponse(HttpResponseMessage response)
{
var statusCode = (int)response.StatusCode;
@@ -302,15 +329,36 @@ namespace Managing.Infrastructure.Evm.Services
{
var value = prop.GetValue(payload);
if (value != null)
{
var paramName = prop.Name;
// Apply camelCase to match JSON property naming
paramName = char.ToLowerInvariant(paramName[0]) + paramName.Substring(1);
// Check if the value is an array or enumerable (but not string)
if (value is IEnumerable enumerable && !(value is string))
{
// Handle arrays by creating multiple query parameters with the same name
foreach (var item in enumerable)
{
if (!isFirst)
{
queryString.Append("&");
}
var paramName = prop.Name;
// Apply camelCase to match JSON property naming
paramName = char.ToLowerInvariant(paramName[0]) + paramName.Substring(1);
queryString.Append(HttpUtility.UrlEncode(paramName));
queryString.Append("=");
queryString.Append(HttpUtility.UrlEncode(item?.ToString() ?? ""));
isFirst = false;
}
}
else
{
// Handle single values
if (!isFirst)
{
queryString.Append("&");
}
queryString.Append(HttpUtility.UrlEncode(paramName));
queryString.Append("=");
@@ -319,6 +367,7 @@ namespace Managing.Infrastructure.Evm.Services
isFirst = false;
}
}
}
return queryString.ToString();
}