Files
managing-apps/src/Managing.Infrastructure.Web3/EvmManager.cs

907 lines
32 KiB
C#

using System.Net.Http.Json;
using System.Numerics;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Common;
using Managing.Core;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Evm;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Models.Proxy;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Services.Gmx;
using NBitcoin;
using Nethereum.Contracts;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Nethereum.HdWallet;
using Nethereum.Hex.HexTypes;
using Nethereum.Signer;
using Nethereum.Web3;
using static Managing.Common.Enums;
using BalanceOfFunction = Nethereum.Contracts.Standards.ERC20.ContractDefinition.BalanceOfFunction;
using BalanceOfOutputDTO = Nethereum.Contracts.Standards.ERC20.ContractDefinition.BalanceOfOutputDTO;
using Chain = Managing.Domain.Evm.Chain;
using TransferEventDTO = Nethereum.Contracts.Standards.ERC721.ContractDefinition.TransferEventDTO;
namespace Managing.Infrastructure.Evm;
public class EvmManager : IEvmManager
{
private readonly Web3 _web3;
private readonly HttpClient _httpClient;
private readonly string _password = "!StrongPassword94";
private readonly IEnumerable<ISubgraphPrices> _subgraphs;
private readonly GmxV2Service _gmxV2Service;
private readonly IWeb3ProxyService _web3ProxyService;
private readonly ICacheService _cacheService;
public EvmManager(IEnumerable<ISubgraphPrices> subgraphs,
IWeb3ProxyService web3ProxyService, ICacheService cacheService)
{
var defaultChain = ChainService.GetEthereum();
_web3 = new Web3(defaultChain.RpcUrl);
_httpClient = new HttpClient();
_subgraphs = subgraphs;
_web3ProxyService = web3ProxyService;
_gmxV2Service = new GmxV2Service();
_cacheService = cacheService;
}
public async Task<decimal> GetAddressBalance(string address)
{
var balance = await _web3.Eth.GetBalance.SendRequestAsync(address);
var etherAmount = Web3.Convert.FromWei(balance.Value);
return etherAmount;
}
public async Task<List<Holder>> GetContractHolders(string contractAddress, DateTime since)
{
var holders = new List<Holder>();
var contract = _web3.Eth.ERC721.GetContractService(contractAddress);
// Retrieve total supply to iterate over all token id generated within the contract
var totalSupply = await contract.TotalSupplyQueryAsync();
for (int tokenId = 1; tokenId < 10; tokenId++)
{
// Retrieve the owner of the nft
var tokenOwner = await contract.OwnerOfQueryAsync(tokenId);
// If holder already have an nft we get the holder
// Otherwise we create a new holder
var holder = holders.FirstOrDefault(h => h.HolderAddress == tokenOwner) ?? new Holder(tokenOwner);
// Retrieve all events related to the owner on the contract address
var nfts = await GetNftEvent(contractAddress, tokenOwner);
// Get tokenId related event
var nftEvent = nfts.FirstOrDefault(n => n.Event.TokenId == new BigInteger(tokenId));
if (nftEvent != null)
{
// Retrieve the date of the nft event that occur in the blocknumber
var blockDate = await GetBlockDate(nftEvent.Log.BlockNumber);
var nft = new Nft(contractAddress, tokenId, blockDate);
// Verify if the date of the holding is before the date passed from the parameters
if (blockDate <= since)
{
holder.AddNft(nft);
// If holder do not exist in the list we add it
if (!holders.Exists(h => h.HolderAddress == tokenOwner))
{
holders.Add(holder);
}
}
else
{
Console.WriteLine($"TokenId #{tokenId} for owner {tokenOwner} date not in range ({blockDate:f})");
}
}
else
{
Console.WriteLine($"Error when getting tokenId #{tokenId} for owner {tokenOwner}");
}
UpdateLine(tokenId, (int)totalSupply, holders.Count);
}
return holders;
}
public async Task<List<EventLog<TransferEventDTO>>>
GetNftEvent(string contractAddress, string tokenOwner)
{
return await NftService.GetNftEvent(_web3, tokenOwner, contractAddress);
}
public async Task<DateTime> GetBlockDate(int blockNumber)
{
return await GetBlockDate(new HexBigInteger(blockNumber));
}
public async Task<DateTime> GetBlockDate(HexBigInteger blockNumber)
{
var block = await _web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(blockNumber);
var date = DateHelpers.GetFromUnixTimestamp((int)block.Timestamp.Value);
return date;
}
private void UpdateLine(int count, int total, int holders)
{
Console.WriteLine($"{count}/{total} - {(((decimal)count * 100m) / (decimal)total)} % - Holders : {holders}");
}
public string VerifySignature(string signature, string message)
{
var signer = new EthereumMessageSigner();
var addressRecovered = signer.EncodeUTF8AndEcRecover(message, signature);
return addressRecovered;
}
public string SignMessage(string message, string privateKey)
{
var signer = new EthereumMessageSigner();
var signature = signer.EncodeUTF8AndSign(message, new EthECKey(privateKey));
return signature;
}
public (string Key, string Secret) GenerateAddress()
{
var mnemo = new Mnemonic(Wordlist.English, WordCount.Twelve);
var wallet = new Wallet(mnemo.ToString(), _password);
var account = wallet.GetAccount(0);
return (account.Address, mnemo.ToString());
}
public string GetAddressFromMnemo(string mnemo)
{
var wallet = new Wallet(mnemo, _password);
return wallet.GetAccount(0).Address;
}
public async Task<EvmBalance> GetEtherBalance(Chain chain, string account)
{
var web3 = new Web3(chain.RpcUrl);
var etherBalance = Web3.Convert.FromWei(await web3.Eth.GetBalance.SendRequestAsync(account));
var lastCandle = await GetCandle(Ticker.ETH);
return new EvmBalance()
{
Balance = etherBalance, Price = lastCandle.Close, TokenName = "ETH", Value = etherBalance * lastCandle.Close
};
}
public async Task<List<EvmBalance>> GetAllBalances(Chain chain, string publicAddress)
{
var balances = new List<EvmBalance>();
foreach (var ticker in Enum.GetValues<Ticker>())
{
try
{
var balance = await GetTokenBalance(chain.Name, ticker, publicAddress);
if (balance != null && balance.Balance > 0)
{
balances.Add(balance);
}
}
catch (Exception ex)
{
// TODO : handle exception
}
}
var etherBalance = await GetEtherBalance(chain, publicAddress);
etherBalance.Chain = chain;
balances.Add(etherBalance);
return balances;
}
public async Task<EvmBalance> GetTokenBalance(string chainName, Ticker ticker, string publicAddress)
{
var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl);
var balanceOfMessage = new BalanceOfFunction() { Owner = publicAddress };
//Creating a new query handler
var queryHandler = web3.Eth.GetContractQueryHandler<BalanceOfFunction>();
var contractAddress = TokenService.GetContractAddress(ticker);
if (contractAddress == Arbitrum.Address.Zero)
return null;
var balance = await queryHandler
.QueryAsync<BigInteger>(contractAddress, balanceOfMessage)
.ConfigureAwait(false);
var lastCandle = await GetCandle(ticker);
var tokenUsdPrice = lastCandle.Close;
var tokenDecimal = TokenService.GetDecimal(ticker);
var balanceFromWei = Web3.Convert.FromWei(balance, tokenDecimal);
var evmBalance = new EvmBalance
{
TokenName = ticker.ToString(),
Balance = balanceFromWei,
TokenAddress = contractAddress,
Value = tokenUsdPrice * balanceFromWei,
Price = tokenUsdPrice,
Chain = chain
};
return evmBalance;
}
public async Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize,
string publicAddress)
{
var callList = new List<IMulticallInputOutput>();
var startItem = (page * pageSize);
var tokens = TokenService.GetTokens();
var totaItemsToFetch = startItem + pageSize <= tokens.Count ? startItem + pageSize : tokens.Count + startItem;
for (int i = startItem; i < totaItemsToFetch; i++)
{
var balanceOfMessage = new BalanceOfFunction() { Owner = publicAddress };
var call = new MulticallInputOutput<BalanceOfFunction, BalanceOfOutputDTO>(balanceOfMessage,
tokens[i].Address);
callList.Add(call);
}
var evmTokens = new List<(EvmBalance Balance, GeckoToken GeckoToken)>();
try
{
var web3 = new Web3(chain.RpcUrl);
var geckoTokens = TokenService.GetGeckoTokens();
await web3.Eth.GetMultiQueryHandler().MultiCallAsync(callList.ToArray());
for (int i = startItem; i < totaItemsToFetch; i++)
{
var balance = ((MulticallInputOutput<BalanceOfFunction, BalanceOfOutputDTO>)callList[i - startItem])
.Output.Balance;
if (balance > 0)
{
var tokenBalance = new EvmBalance()
{
Balance = Web3.Convert.FromWei(balance, tokens[i].Decimals),
TokenName = tokens[i].Symbol,
TokenAddress = tokens[i].Address,
Chain = chain
};
var geckoToken = geckoTokens.FirstOrDefault(x =>
string.Equals(x.Symbol, tokens[i].Symbol, StringComparison.InvariantCultureIgnoreCase));
evmTokens.Add((tokenBalance, geckoToken)!);
}
}
if (evmTokens.Count > 0)
{
var ids = evmTokens.Select(x => x.GeckoToken?.Id).Distinct().ToList();
var prices = await GetPrices(ids).ConfigureAwait(false);
foreach (var balance in evmTokens)
{
if (balance.GeckoToken != null)
{
var price = prices[balance.GeckoToken.Id.ToLower()];
balance.Balance.Price = price["usd"];
balance.Balance.Value = balance.Balance.Price * balance.Balance.Balance;
}
}
}
}
catch (Exception ex)
{
// TODO : Handle error
// No enable to reach rpc
var test = ex.Message;
}
return evmTokens.Select(e => e.Balance).ToList();
}
public async Task<Dictionary<string, Dictionary<string, decimal>>> GetPrices(List<string> geckoIds)
{
var idsCombined = string.Join(",", geckoIds);
return await _httpClient.GetFromJsonAsync<Dictionary<string, Dictionary<string, decimal>>>(
"https://api.coingecko.com/api/v3/simple/price?ids=" + idsCombined + "&vs_currencies=usd");
}
public async Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress)
{
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,
bool isFirstCall = false)
{
string gmxTimeframe = GmxHelpers.GeTimeframe(timeframe);
int limit = isFirstCall ? 10000 : CalculateCandleLimit(startDate, timeframe);
GmxV2Prices? gmxPrices = null;
int maxRetries = 3;
int delayMs = 1000;
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
gmxPrices = await _httpClient.GetFromJsonAsync<GmxV2Prices>(
$"https://arbitrum-api.gmxinfra.io/prices/candles?tokenSymbol={ticker}&period={gmxTimeframe}&limit={limit}");
break;
}
catch (HttpRequestException ex) when (ex.InnerException is IOException)
{
Console.Error.WriteLine($"Attempt {attempt}: Network error while fetching candles: {ex.Message}");
if (attempt == maxRetries)
throw;
await Task.Delay(delayMs * attempt);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Unexpected error: {ex.Message}");
throw;
}
}
if (gmxPrices == null)
return null;
var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.ToUnixTimestamp()).ToList();
var candles = new List<Candle>();
var timeBetweenCandles =
gmxPrices.Candles.Count > 2 ? gmxPrices.Candles[0][0] - gmxPrices.Candles[1][0] : 900; // Default 15 minutes
for (int i = 0; i < filteredCandles.Count; i++)
{
var c = GmxV2Mappers.Map(filteredCandles[i], ticker, timeframe, (int)timeBetweenCandles);
candles.Add(c);
}
return candles.OrderBy(c => c.Date).ToList();
}
private int CalculateCandleLimit(DateTime startDate, Timeframe timeframe)
{
var now = DateTime.UtcNow;
var minutesPerCandle = timeframe switch
{
Timeframe.OneMinute => 1,
Timeframe.FiveMinutes => 5,
Timeframe.FifteenMinutes => 15,
Timeframe.ThirtyMinutes => 30,
Timeframe.OneHour => 60,
Timeframe.FourHour => 240,
Timeframe.OneDay => 1440,
_ => 15
};
var totalMinutes = (now - startDate).TotalMinutes;
var candlesNeeded = (int)Math.Ceiling(totalMinutes / minutesPerCandle);
return Math.Min(candlesNeeded + 5, 10000);
}
public decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker)
{
var subgraph = GetSubgraph(subgraphProvider);
var volume = subgraph.GetVolume(ticker).Result;
return volume;
}
private ISubgraphPrices GetSubgraph(SubgraphProvider subgraphProvider)
{
return _subgraphs.First(s => s.GetProvider() == subgraphProvider);
}
public async Task<List<Ticker>> GetAvailableTicker()
{
var tokenList = await _httpClient.GetFromJsonAsync<GmxV2TokenList>(
"https://arbitrum-api.gmxinfra.io/tokens");
if (tokenList == null)
return null;
return GmxV2Mappers.Map(tokenList);
}
public async Task<Candle> GetCandle(Ticker ticker)
{
var key = $"lastcandle-{ticker}";
var cachedCandle = _cacheService.GetValue<Candle>(key);
if (cachedCandle == null)
{
var lastCandles = await GetCandles(ticker, DateTime.UtcNow.AddMinutes(-5),
Timeframe.OneMinute);
cachedCandle = lastCandles.Last();
_cacheService.SaveValue(key, cachedCandle, TimeSpan.FromMinutes(5));
}
return cachedCandle;
}
public async Task<PrivyInitAddressResponse> InitAddress(string publicAddress)
{
try
{
var response = await _web3ProxyService.CallPrivyServiceAsync<PrivyInitAddressResponse>(
"/init-address",
new { address = publicAddress });
return response;
}
catch (Exception ex)
{
// Log the error
Console.Error.WriteLine($"Error initializing address: {ex.Message}");
return new PrivyInitAddressResponse
{
Success = false,
Error = ex.Message
};
}
}
public async Task<BigInteger> GetAllowance(string publicAddress, Ticker ticker)
{
var contractAddress = TokenService.GetContractAddress(ticker);
var allowance = await EvmBase.GetAllowance(_web3, publicAddress, contractAddress);
return allowance;
}
public async Task<bool> Send(
Chain chain,
Ticker ticker,
decimal amount,
string publicAddress,
string privateKey,
string receiverAddress)
{
var account = new Wallet(privateKey, _password).GetAccount(publicAddress);
var web3 = new Web3(account, chain.RpcUrl);
try
{
if (ticker == Ticker.ETH)
{
return await SendEth(amount, receiverAddress, web3);
}
return await SendToken(ticker, amount, publicAddress, receiverAddress, web3);
}
catch (Exception ex)
{
return false;
}
}
private static async Task<bool> SendEth(decimal amount, string receiverAddress, Web3 web3)
{
web3.TransactionManager.UseLegacyAsDefault = true;
var ethService = web3.Eth.GetEtherTransferService();
var gas = await ethService.EstimateGasAsync(receiverAddress, amount);
var transaction = await ethService.TransferEtherAndWaitForReceiptAsync(receiverAddress, amount, gas: gas);
return transaction.Status.Value == 1;
}
private static async Task<bool> SendToken(
Ticker ticker,
decimal amount,
string senderAddress,
string receiverAddress,
Web3 web3)
{
var contractAddress = TokenService.GetContractAddress(ticker);
var transactionMessage = new TransferFunction
{
FromAddress = senderAddress,
To = receiverAddress,
Value = Web3.Convert.ToWei(amount)
};
var transferHandler = web3.Eth.GetContractTransactionHandler<TransferFunction>();
var transferReceipt =
await transferHandler.SendRequestAndWaitForReceiptAsync(contractAddress, transactionMessage);
var transaction =
await web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(transferReceipt.TransactionHash);
return transaction != null;
}
public async Task<bool> CancelOrders(Account account, Ticker ticker)
{
if (account.IsPrivyWallet)
{
try
{
var response = await _web3ProxyService.CallGmxServiceAsync<Web3ProxyResponse>("/cancel-orders",
new
{
account = account.Key,
walletId = account.Secret,
ticker = ticker.ToString()
});
return response.Success;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error canceling orders via Fastify API: {ex.Message}");
return false;
}
}
else
{
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
var web3 = new Web3(wallet, chain.RpcUrl);
var service = new GmxV2Service();
return await service.CancelOrders(web3, account.Key, ticker);
}
}
public async Task<Trade> IncreasePosition(
Account account,
Ticker ticker,
TradeDirection direction,
decimal price,
decimal quantity,
decimal? leverage,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
{
Trade trade = null;
try
{
// If this is a Privy wallet, call the GMX service through Fastify API
if (account.IsPrivyWallet)
{
try
{
var response = await _web3ProxyService.CallGmxServiceAsync<object>("/open-position",
new
{
account = account.Key,
walletId = account.Secret,
tradeType = price > 0 ? "limit" : "market",
ticker = ticker.ToString(),
direction = direction.ToString(),
price = price,
quantity,
leverage = leverage ?? 1.0m,
stopLossPrice = stopLossPrice,
takeProfitPrice = takeProfitPrice
});
// Create a trade object using the returned hash
var tradeType = price > 0 ? TradeType.Limit : TradeType.Market;
var tradeStatus = TradeStatus.Requested; // Use a valid enum value that exists in TradeStatus
trade = new Trade(
DateTime.UtcNow,
direction,
tradeStatus,
tradeType,
ticker,
quantity,
price,
leverage ?? 1.0m,
account.Key,
""
);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
else
{
// Continue with the existing direct service call for non-Privy wallets
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
var web3 = new Web3(wallet, chain.RpcUrl);
trade = await _gmxV2Service.IncreasePosition(web3, account.Key, ticker, direction, price, quantity,
leverage);
}
}
catch (Exception ex)
{
throw;
}
return trade;
}
public async Task<Trade> DecreasePosition(
Account account,
Ticker ticker,
TradeDirection direction,
decimal price,
decimal quantity,
decimal? leverage)
{
Trade trade = null;
if (account.IsPrivyWallet)
{
try
{
var response = await _web3ProxyService.CallGmxServiceAsync<ClosePositionResponse>("/close-position",
new
{
account = account.Key,
ticker = ticker.ToString(),
direction = direction == TradeDirection.Long
? TradeDirection.Short.ToString()
: TradeDirection.Long.ToString(),
});
trade = new Trade(
DateTime.UtcNow,
direction,
TradeStatus.Requested,
TradeType.Market,
ticker,
quantity,
price,
leverage ?? 1,
response.Hash,
""
);
return trade;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
var web3 = new Web3(wallet, chain.RpcUrl);
try
{
trade = await _gmxV2Service.DecreasePosition(web3, account.Key, ticker, direction, price, quantity,
leverage ?? 1);
}
catch (Exception ex)
{
throw;
}
return trade;
}
public async Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker,
TradeDirection direction, decimal price, decimal quantity, decimal? leverage,
decimal? stopLossPrice = null,
decimal? takeProfitPrice = null)
{
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
var web3 = new Web3(wallet, chain.RpcUrl);
Trade trade;
try
{
// TODO: This method in GmxV2Service might not exist or needs different handling for Privy wallets.
// Commenting out for now as IncreasePosition is the priority.
// trade = await _gmxV2Service.DecreaseOrder(web3, account.Key, tradeType, ticker, direction, price,
// quantity, leverage);
trade = null; // Placeholder return
}
catch (Exception ex)
{
throw;
}
return trade;
}
public async Task<Trade> GetTrade(Account account, string chainName, Ticker ticker)
{
if (account.IsPrivyWallet)
{
var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxPositionsResponse>(
"/positions",
new { account = account.Key, ticker = ticker.ToString() });
var position = result.Positions.FirstOrDefault(p => p.Ticker == ticker.ToString());
if (position == null)
return null;
// TODO: Map the position object to a Trade object
var trade = new Trade(
position.Date,
MiscExtensions.ParseEnum<TradeDirection>(position.Direction),
MiscExtensions.ParseEnum<TradeStatus>(position.Status),
MiscExtensions.ParseEnum<TradeType>(position.TradeType),
MiscExtensions.ParseEnum<Ticker>(position.Ticker),
(decimal)position.Quantity,
(decimal)position.Price,
(decimal?)position.Leverage,
account.Key,
position.ExchangeOrderId
);
return trade;
}
return await GetTrade(account.Key, chainName, ticker);
}
public async Task<List<Position>> GetPositions(Account account)
{
if (account.IsPrivyWallet)
{
var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxPositionsResponse>(
"/positions",
new { account = account.Key });
return GmxV2Mappers.Map(result.Positions);
}
throw new NotImplementedException();
}
public async Task<Trade> GetTrade(string reference, string chainName, Ticker ticker)
{
var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl);
return await _gmxV2Service.GetTrade(web3, reference, ticker);
}
public async Task<List<FundingRate>> GetFundingRates()
{
return await _web3ProxyService.CallGmxServiceAsync<List<FundingRate>>("/gmx/funding-rates", new { });
}
public async Task<decimal> QuantityInPosition(string chainName, Account account, Ticker ticker)
{
if (account.IsPrivyWallet)
{
var positions = await GetPositions(account);
var positionForTicker = positions.FirstOrDefault(p => p.Ticker == ticker);
if (positionForTicker != null)
{
return positionForTicker.Open.Quantity;
}
else
{
return 0;
}
}
var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl);
var quantity = await _gmxV2Service.QuantityInPosition(web3, account.Key, ticker);
return quantity;
}
public async Task<decimal> GetFee(string chainName)
{
var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl);
var etherPrice = (await GetPrices(new List<string> { "ethereum" }))["ethereum"]["usd"];
var fee = await GmxService.GetFee(web3, etherPrice);
return fee;
}
public async Task<List<Trade>> GetOrders(Account account, Ticker ticker)
{
if (account.IsPrivyWallet)
{
var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxTradesResponse>("/trades",
new { account = account.Key, ticker = ticker.ToString() });
return GmxV2Mappers.Map(result.Trades);
}
else
{
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
var web3 = new Web3(chain.RpcUrl);
// var orders = await GmxService.GetOrders(web3, account.Key, ticker);
var orders = await _gmxV2Service.GetOrders(web3, account.Key, ticker);
return GmxV2Mappers.Map(orders);
}
return new List<Trade>();
}
public async Task<bool> SetAllowance(Account account, Ticker ticker, BigInteger amount)
{
if (account.IsPrivyWallet)
{
var allowance = await _web3ProxyService.CallPrivyServiceAsync<PrivyApproveTokenResponse>("/approve-token",
new
{
address = account.Key, walletId = account.Secret, ticker = ticker.ToString(),
amount = amount.Equals(0) ? null : amount.ToString()
});
return false;
}
var web3 = BuildWeb3ForAccount(account);
var contractAddress = TokenService.GetContractAddress(ticker);
var approval = await EvmBase.ApproveToken(web3, account.Key, contractAddress,
Arbitrum.AddressV2.SyntheticsRouter, amount);
return approval;
}
private Web3 BuildWeb3ForAccount(Account account)
{
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
return new Web3(wallet, chain.RpcUrl);
}
public async Task<(string Id, string Address)> CreatePrivyWallet()
{
var privyWallet = await _web3ProxyService.CallPrivyServiceAsync<PrivyWallet>("/privy/create-wallet", new { });
return (privyWallet.Id, privyWallet.Address);
}
/// <summary>
/// Signs a message using the embedded wallet
/// </summary>
/// <param name="embeddedWalletId">The wallet id of the embedded wallet</param>
/// <param name="address">The address of the embedded wallet</param>
/// <param name="message">The message to sign</param>
/// <returns>The signature response</returns>
public async Task<string> SignMessageAsync(string embeddedWalletId, string address, string message)
{
// Construct the request body using the exact format from Privy documentati
var requestBody = new
{
address = address,
walletId = embeddedWalletId,
message = message,
};
var response =
await _web3ProxyService.CallPrivyServiceAsync<PrivySigningResponse>("sign-message", requestBody);
return response.Signature;
}
// Overload to match IEvmManager interface
public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe)
{
return await GetCandles(ticker, startDate, timeframe, false);
}
}