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); 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<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"); throw new ArgumentException($"Account '{name}' not found");
} }
ManageProperties(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
if (account.User == null && account.User != null) 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) public async Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance)
{ {
var account = await _accountRepository.GetAccountByKeyAsync(key); var account = await _accountRepository.GetAccountByKeyAsync(key);
ManageProperties(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
return account; return account;
} }
@@ -127,7 +127,7 @@ public class AccountService : IAccountService
public async Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance) public async Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance)
{ {
var account = await _accountRepository.GetAccountByNameAsync(name); var account = await _accountRepository.GetAccountByNameAsync(name);
ManageProperties(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
return account; return account;
} }
@@ -139,7 +139,7 @@ public class AccountService : IAccountService
if (account != null) if (account != null)
{ {
ManageProperties(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
if (account.User != null) if (account.User != null)
{ {
account.User = await _userRepository.GetUserByNameAsync(account.User.Name); account.User = await _userRepository.GetUserByNameAsync(account.User.Name);
@@ -161,7 +161,7 @@ public class AccountService : IAccountService
foreach (var account in result) foreach (var account in result)
{ {
ManageProperties(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
accounts.Add(account); accounts.Add(account);
} }
@@ -190,7 +190,7 @@ public class AccountService : IAccountService
foreach (var account in result.Where(a => a.User.Name == user.Name)) foreach (var account in result.Where(a => a.User.Name == user.Name))
{ {
ManageProperties(hideSecrets, getBalance, account); await ManagePropertiesAsync(hideSecrets, getBalance, account);
accounts.Add(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) if (account != null)
{ {
@@ -341,7 +341,7 @@ public class AccountService : IAccountService
{ {
try try
{ {
account.Balances = _exchangeService.GetBalances(account).Result; account.Balances = await _exchangeService.GetBalances(account);
} }
catch (Exception ex) 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") b.Property<DateTime?>("Runtime")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<decimal>("TotalBalance")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("TotalPnL") b.Property<decimal>("TotalPnL")
.HasColumnType("decimal(18,8)"); .HasColumnType("decimal(18,8)");

View File

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

View File

@@ -325,15 +325,59 @@ public class EvmManager : IEvmManager
public async Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress) public async Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress)
{ {
var chainBalances = new List<EvmBalance>(); try
var chains = ChainService.GetChains();
foreach (var chain in chains)
{ {
chainBalances.AddRange(await GetAllBalances(chain, publicAddress)); // Define the assets and chains we want to query
} var assets = new[] { Ticker.USDC, Ticker.ETH };
var chains = new[] { "arbitrum", "ethereum" };
return chainBalances; // 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();
foreach (var chain in chains)
{
chainBalances.AddRange(await GetAllBalances(chain, publicAddress));
}
return chainBalances;
}
} }
public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe, public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe,

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Managing.Domain.Accounts;
namespace Managing.Infrastructure.Evm.Models.Proxy namespace Managing.Infrastructure.Evm.Models.Proxy
{ {
@@ -138,4 +139,16 @@ namespace Managing.Infrastructure.Evm.Models.Proxy
[JsonPropertyName("hash")] [JsonPropertyName("hash")]
public string Hash { get; set; } 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.Net.Http.Json;
using System.Text; using System.Text;
using System.Text.Json; 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 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 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) private async Task HandleErrorResponse(HttpResponseMessage response)
{ {
var statusCode = (int)response.StatusCode; var statusCode = (int)response.StatusCode;
@@ -303,20 +330,42 @@ namespace Managing.Infrastructure.Evm.Services
var value = prop.GetValue(payload); var value = prop.GetValue(payload);
if (value != null) if (value != null)
{ {
if (!isFirst)
{
queryString.Append("&");
}
var paramName = prop.Name; var paramName = prop.Name;
// Apply camelCase to match JSON property naming // Apply camelCase to match JSON property naming
paramName = char.ToLowerInvariant(paramName[0]) + paramName.Substring(1); paramName = char.ToLowerInvariant(paramName[0]) + paramName.Substring(1);
queryString.Append(HttpUtility.UrlEncode(paramName)); // Check if the value is an array or enumerable (but not string)
queryString.Append("="); if (value is IEnumerable enumerable && !(value is string))
queryString.Append(HttpUtility.UrlEncode(value.ToString())); {
// Handle arrays by creating multiple query parameters with the same name
foreach (var item in enumerable)
{
if (!isFirst)
{
queryString.Append("&");
}
isFirst = false; 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("=");
queryString.Append(HttpUtility.UrlEncode(value.ToString()));
isFirst = false;
}
} }
} }