docker files fixes from liaqat

This commit is contained in:
alirehmani
2024-05-03 16:39:25 +05:00
commit 464a8730e8
587 changed files with 44288 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
using Managing.Domain.Candles;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Abstractions;
public interface ISubgraphPrices
{
public SubgraphProvider GetProvider();
Task<IEnumerable<Candle>> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe);
Task<decimal> GetVolume(Ticker ticker);
Task<IEnumerable<Ticker>> GetTickers();
}

View File

@@ -0,0 +1,9 @@
using Managing.Infrastructure.Evm.Subgraphs.Models;
namespace Managing.Infrastructure.Evm.Abstractions;
public interface IUniswap : ISubgraphPrices
{
Task<Pools> GetMostLiquidMarketPairs();
Task<TopTokens> GetTopTokens();
}

View File

@@ -0,0 +1,620 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Evm;
using NBitcoin;
using Nethereum.Contracts;
using Nethereum.Hex.HexTypes;
using Nethereum.Signer;
using Nethereum.Web3;
using Nethereum.HdWallet;
using System.Numerics;
using System.Net.Http.Json;
using Managing.Infrastructure.Evm.Services;
using Managing.Domain.Candles;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Core;
using static Managing.Common.Enums;
using Managing.Infrastructure.Evm.Services.Gmx;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Managing.Common;
using Managing.Domain.Trades;
using Managing.Domain.Accounts;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Referentials;
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 Dictionary<string, Dictionary<string, decimal>> _geckoPrices;
public EvmManager(IEnumerable<ISubgraphPrices> subgraphs)
{
var defaultChain = ChainService.GetEthereum();
_web3 = new Web3(defaultChain.RpcUrl);
_httpClient = new HttpClient();
_subgraphs = subgraphs;
_geckoPrices = _geckoPrices != null && _geckoPrices.Any() ? _geckoPrices : new Dictionary<string, Dictionary<string, decimal>>();
SetupPrices();
}
public void SetupPrices()
{
try
{
var geckoIds = new List<string>();
foreach (var ticker in Enum.GetValues<Ticker>())
{
var geckoId = TokenService.GetGeckoToken(ticker.ToString())?.Id;
if (geckoId != null)
{
geckoIds.Add(geckoId);
}
}
if (geckoIds != null && geckoIds.Count > 0 && !_geckoPrices.Any())
{
_geckoPrices = GetPrices(geckoIds).Result;
}
}
catch (Exception ex)
{
// TODO : Handle error
}
}
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<Nethereum.Contracts.Standards.ERC721.ContractDefinition.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(Domain.Evm.Chain chain, string account)
{
var web3 = new Web3(chain.RpcUrl);
var etherBalance = Web3.Convert.FromWei(await web3.Eth.GetBalance.SendRequestAsync(account));
var etherPrice = (await GetPrices(new List<string> { "ethereum"}))["ethereum"]["usd"];
return new EvmBalance() { Balance = etherBalance, Price = etherPrice, TokenName = "ETH", Value = etherBalance * etherPrice };
}
public async Task<List<EvmBalance>> GetAllBalances(Domain.Evm.Chain chain, string publicAddress)
{
var balances = new List<EvmBalance>();
var web3 = new Web3(chain.RpcUrl);
SetupPrices();
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);
//var pageSize = 50;
//var tokenCount = TokenService.GetTokens().Count;
//for (int i = 0; i < (tokenCount / pageSize); i++)
//{
// var pageBalances = await GetBalances(chain, i, pageSize, publicAddress).ConfigureAwait(false);
// balances.AddRange(pageBalances);
//}
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 geckoId = TokenService.GetGeckoToken(ticker.ToString())?.Id;
if (geckoId == null)
return null;
var tokenUsdPrice = _geckoPrices[geckoId][Constants.Stablecoins.Usd.ToLowerInvariant()];
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(Domain.Evm.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
}
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(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate, Timeframe timeframe)
{
string gmxTimeframe = GmxHelpers.GeTimeframe(timeframe);
var gmxPrices = await _httpClient.GetFromJsonAsync<GmxPrices>($"https://stats.gmx.io/api/candles/{ticker}?preferableChainId=42161&period={gmxTimeframe}&from={startDate.ToUnixTimestamp()}&preferableSource=fast");
//var subgraph = _subgraphs.First(s => s.GetProvider() == subgraphProvider);
//var prices = await subgraph.GetPrices(ticker, startDate, timeframe);
//if (prices == null)
//{
// foreach (var subgraphFallback in _subgraphs.Where(s => s.GetProvider() != subgraphProvider))
// {
// prices = await subgraphFallback.GetPrices(ticker, startDate, timeframe);
// if (prices != null)
// break;
// }
//}
if (gmxPrices == null)
return null;
gmxPrices.prices.RemoveAt(gmxPrices.prices.Count - 1);
return gmxPrices.prices.Select(p => GmxMappers.Map(p, ticker, timeframe)).ToList();
}
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 subgraph = GetSubgraph(SubgraphProvider.Gbc);
var pairs = await subgraph.GetTickers();
return pairs.ToList();
}
public async Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker)
{
var lastPrices = await GetCandles(subgraphProvider, ticker, DateTime.UtcNow.AddMinutes(-15), Timeframe.FiveMinutes);
return lastPrices.Last();
}
public async Task<bool> InitAddress(string chainName, string publicAddress, string privateKey)
{
try
{
var chain = ChainService.GetChain(chainName);
var account = new Wallet(privateKey, _password).GetAccount(publicAddress);
var web3 = new Web3(account, chain.RpcUrl);
var tickers = await GetAvailableTicker();
await GmxService.InitAccountForTrading(web3, publicAddress, tickers);
return true;
}
catch (Exception ex)
{
return false;
}
}
public async Task<bool> ApproveTicker(string publicAddress, string privateKey, Ticker ticker)
{
try
{
var account = new Wallet(privateKey, _password).GetAccount(publicAddress);
var contractAddress = TokenService.GetContractAddress(ticker);
await GmxService.ApproveToken(_web3, publicAddress, contractAddress);
return true;
}
catch (Exception ex)
{
return false;
}
}
public async Task<bool> Send(
Domain.Evm.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);
}
else
{
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)
{
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
var web3 = new Web3(wallet, chain.RpcUrl);
return await GmxService.CancelOrders(web3, account.Key, ticker);
}
public async Task<Trade> IncreasePosition(
Account account,
Ticker ticker,
TradeDirection direction,
decimal price,
decimal quantity,
decimal? leverage)
{
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 = null;
try
{
trade = await GmxService.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)
{
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 = null;
try
{
trade = await GmxService.DecreasePosition(web3, account.Key, ticker, direction, price, quantity, leverage);
}
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)
{
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 = null;
try
{
trade = await GmxService.DecreaseOrder(web3, tradeType, account.Key, ticker, direction, price, quantity, leverage);
}
catch (Exception ex)
{
throw;
}
return trade;
}
public async Task<Trade> GetTrade(Account account, string chainName, Ticker ticker)
{
return await GetTrade(account.Key, chainName, ticker);
}
public async Task<Trade> GetTrade(string reference, string chainName, Ticker ticker)
{
var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl);
return await GmxService.GetTrade(web3, reference, ticker);
}
public async Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker)
{
var chain = ChainService.GetChain(chainName);
var web3 = new Web3(chain.RpcUrl);
var quantity = await GmxService.QuantityInPosition(web3, publicAddress, 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)
{
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 orders = await GmxService.GetOrders(web3, account.Key, ticker);
return GmxHelpers.Map(orders, ticker);
}
}

View File

@@ -0,0 +1,75 @@
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Infrastructure.Evm.Subgraphs.Models;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Extensions;
public static class PriceExtensions
{
public static List<Candle> GetCandles(this Round[] prices, Timeframe timeframe, Ticker ticker)
{
int timezoneOffset = - (int)(new DateTimeOffset(DateTime.UtcNow).Offset.TotalSeconds);
var CHART_PERIODS = new Dictionary<Timeframe, int>
{
{ Timeframe.FiveMinutes, 60 * 5 },
{ Timeframe.FifteenMinutes, 60 * 15 },
{ Timeframe.OneHour, 60 * 60 },
{ Timeframe.FourHour, 60 * 60 * 4 },
{ Timeframe.OneDay, 60 * 60 * 24 }
};
int periodTime = CHART_PERIODS[timeframe];
if (prices.Count() < 2)
{
return new List<Candle>();
}
List<Candle> candles = new List<Candle>();
Round first = prices[0];
int prevTsGroup = (int)Math.Floor((decimal)first.UnixTimestamp / periodTime) * periodTime;
decimal prevPrice = decimal.Parse(first.Value);
decimal o = prevPrice;
decimal h = prevPrice;
decimal l = prevPrice;
decimal c = prevPrice;
for (int i = 1; i < prices.Count(); i++)
{
var current = prices[i];
int tsGroup = (int)Math.Floor((decimal)current.UnixTimestamp / periodTime) * periodTime;
if (prevTsGroup != tsGroup)
{
candles.Add(new Candle
{
OpenTime = DateHelpers.GetFromUnixTimestamp(prevTsGroup + timezoneOffset),
Date = DateHelpers.GetFromUnixTimestamp(tsGroup + timezoneOffset),
Open = o,
High = h,
Low = l,
Close = c,
Timeframe = timeframe
});
o = c;
h = Math.Max(o, c);
l = Math.Min(o, c);
}
c = decimal.Parse(current.Value);
h = Math.Max(h, c);
l = Math.Min(l, c);
prevTsGroup = tsGroup;
}
return candles.Select(x => new Candle
{
OpenTime = x.OpenTime,
Date = x.Date,
Open = x.Open,
Close = x.Close,
High = x.High,
Low = x.Low,
Timeframe = x.Timeframe,
Exchange = TradingExchanges.Evm,
Ticker = ticker.ToString()
}).ToList();
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GraphQL.Client" Version="6.0.0" />
<PackageReference Include="GraphQL.Client.Abstractions" Version="6.0.0" />
<PackageReference Include="GraphQL.Client.Serializer.SystemTextJson" Version="6.0.0" />
<PackageReference Include="GraphQL.Query.Builder" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="NBitcoin" Version="7.0.27" />
<PackageReference Include="Nethereum.HdWallet" Version="4.16.0" />
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="4.16.0" />
<PackageReference Include="Nethereum.Web3" Version="4.16.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj" />
<ProjectReference Include="..\Managing.Tools.ABI\Managing.Tools.ABI.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
namespace Managing.Domain.Evm;
public class GeckoToken
{
public string Id { get; set; }
public string Symbol { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.Numerics;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Models.Gmx;
public class GmxOrder
{
public string CollateralToken { get; internal set; }
public string IndexToken { get; internal set; }
public string CollateralDelta { get; internal set; }
public string SizeDelta { get; internal set; }
public string PurchaseToken { get; internal set; }
public string PurchaseTokenAmount { get; internal set; }
public bool IsLong { get; internal set; }
public string TriggerPrice { get; internal set; }
public bool TriggerAboveThreshold { get; internal set; }
public GmxOrderType Type { get; internal set; }
public BigInteger Index { get; internal set; }
}

View File

@@ -0,0 +1,9 @@
namespace Managing.Infrastructure.Evm.Models.Gmx;
public class GmxOrderIndexes
{
public int SwapIndex { get; set; }
public int IncreaseIndex { get; set; }
public int DecreaseIndex { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.Numerics;
namespace Managing.Infrastructure.Evm.Models.Gmx;
public class GmxPosition
{
public string CollateralToken { get; set; }
public string IndexToken { get; set; }
public bool IsLong { get; set; }
public BigInteger SizeDelta { get; set; }
public BigInteger Collateral { get; set; }
public BigInteger AveragePrice { get; set; }
public BigInteger EntryFundingRate { get; set; }
public bool HasRealisedProfit { get; set; }
public BigInteger RealisedPnl { get; set; }
public BigInteger HasProfit { get; set; }
public BigInteger Delta { get; set; }
public BigInteger LastIncreasedTime { get; internal set; }
}

View File

@@ -0,0 +1,19 @@
namespace Managing.Infrastructure.Evm.Models.Gmx;
public class GmxPrices
{
public List<GmxOhlc> prices { get; set; }
public string period { get; set; }
public int updatedAt { get; set; }
}
public class GmxOhlc
{
public int t { get; set; }
public double o { get; set; }
public double c { get; set; }
public double h { get; set; }
public double l { get; set; }
}

View File

@@ -0,0 +1,20 @@
namespace Managing.Infrastructure.Evm.Models;
public class TradaoList
{
public IList<Row> row { get; set; }
public int total { get; set; }
public int updatetime { get; set; }
}
public class Row
{
public string user { get; set; }
public string pnl { get; set; }
public string roi { get; set; }
public string longSize { get; set; }
public string shortSize { get; set; }
public string openLatestCollateralSum { get; set; }
public string chainId { get; set; }
}

View File

@@ -0,0 +1,49 @@
namespace Managing.Infrastructure.Evm.Models;
public class TradaoUserDetails
{
public int updatetime { get; set; }
public Summary summary { get; set; }
public IList<Preference> preference { get; set; }
public IList<OpenPositions> openPositions { get; set; }
}
public class Summary
{
public string pnl { get; set; }
public string roi { get; set; }
public double winRate { get; set; }
public int trades { get; set; }
public int winTrades { get; set; }
public int lossTrades { get; set; }
public string averageWin { get; set; }
public string averageLoss { get; set; }
public string openCollateralSum { get; set; }
public string fee { get; set; }
}
public class Preference
{
public string tokenAddress { get; set; }
public int winTrades { get; set; }
public int lossTrades { get; set; }
public string pnl { get; set; }
}
public class OpenPositions
{
public string indexTokenAddress { get; set; }
public string collateralTokenAddress { get; set; }
public bool isLong { get; set; }
public string position { get; set; }
public string collateral { get; set; }
public string realizedPnl { get; set; }
public string averagePrice { get; set; }
public string totalFee { get; set; }
public string entryFundingRate { get; set; }
public string borrowFee { get; set; }
public string closeFee { get; set; }
public string liqPrice { get; set; }
}

View File

@@ -0,0 +1,57 @@
namespace Managing.Infrastructure.Evm.Referentials;
public class Arbitrum
{
public class Address
{
public const string ETH = "0x82af49447d8a07e3bd95bd0d56f35241523fbab1";
public const string WBTC = "0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f";
public const string LINK = "0xf97f4df75117a78c1a5a0dbb814af92458539fb4";
public const string UNI = "0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0";
public const string USDC = "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8";
public const string USDT = "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9";
public const string DAI = "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1";
public const string MIM = "0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A";
public const string FRAX = "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F";
public const string Vault = "0x489ee077994B6658eAfA855C308275EAd8097C4A";
public const string VaultPriceFeed = "0x2d68011bcA022ed0E474264145F46CC4de96a002";
public const string Router = "0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064";
public const string VaultReader = "0xfebB9f4CAC4cD523598fE1C5771181440143F24A";
public const string Reader = "0xF09eD52638c22cc3f1D7F5583e3699A075e601B2";
public const string GlpManager = "0x321F653eED006AD1C29D174e17d96351BDe22649";
public const string RewardRouter = "0xc73d553473dC65CE56db96c58e6a091c20980fbA";
public const string RewardReader = "0xe725Ad0ce3eCf68A7B93d8D8091E83043Ff12e9A";
public const string GLP = "0x4277f8f2c384827b5273592ff7cebd9f2c1ac258";
public const string GMX = "0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a";
public const string ES_GMX = "0xf42ae1d54fd613c9bb14810b0588faaa09a426ca";
public const string BN_GMX = "0x35247165119B69A40edD5304969560D0ef486921";
public const string USDG = "0x45096e7aA921f27590f8F19e457794EB09678141";
public const string StakedGmxTracker = "0x908C4D94D34924765f1eDc22A1DD098397c59dD4";
public const string BonusGmxTracker = "0x4d268a7d4C16ceB5a606c173Bd974984343fea13";
public const string FeeGmxTracker = "0xd2D1162512F927a7e282Ef43a362659E4F2a728F";
public const string FeeGlpTracker = "0x4e971a87900b931fF39d1Aad67697F49835400b6";
public const string StakedGlpTracker = "0x1aDDD80E6039594eE970E5872D247bf0414C8903";
public const string StakedGmxDistributor = "0x23208B91A98c7C1CD9FE63085BFf68311494F193";
public const string StakedGlpDistributor = "0x60519b48ec4183a61ca2B8e37869E675FD203b34";
public const string GmxVester = "0x199070DDfd1CFb69173aa2F7e20906F26B363004";
public const string GlpVester = "0xA75287d2f8b217273E7FCD7E86eF07D33972042E";
public const string OrderBook = "0x09f77E8A13De9a35a7231028187e9fD5DB8a2ACB";
public const string OrderExecutor = "0x7257ac5D0a0aaC04AA7bA2AC0A6Eb742E332c3fB";
public const string OrderBookReader = "0xa27C20A7CF0e1C68C0460706bB674f98F362Bc21";
public const string FastPriceFeed = "0x1a0ad27350cccd6f7f168e052100b4960efdb774";
public const string PositionRouter = "0xb87a436B93fFE9D75c5cFA7bAcFff96430b09868";
public const string PositionManager = "0x87a4088Bd721F83b6c2E5102e2FA47022Cb1c831";
public const string UniswapGmxEthPool = "0x80A9ae39310abf666A87C743d6ebBD0E8C42158E";
public static string Zero = "0x0000000000000000000000000000000000000000";
}
}

View File

@@ -0,0 +1,70 @@
using Managing.Common;
using Managing.Domain.Evm;
namespace Managing.Infrastructure.Evm.Services;
public static class ChainService
{
//private const string RPC_ARBITRUM = "https://convincing-smart-arm.arbitrum-mainnet.discover.quiknode.pro/561ad3fa1db431a2c728c2fdb1a62e8f94acf703/";
private const string RPC_ARBITRUM = "https://arb1.arbitrum.io/rpc";
private const string RPC_ARBITRUM_GOERLI = "https://arb-goerli.g.alchemy.com/v2/ZMkIiKtNvgY03UtWOjho0oqkQrNt_pyc";
private const string RPC_ETHEREUM = "https://mainnet.infura.io/v3/58f44d906ab345beadd03dd2b76348af";
private const string RPC_ETHEREUM_GOERLI = "https://eth-goerli.g.alchemy.com/v2/xbc-eM-vxBmM9Uf1-RjjGjLp8Ng-FIc6";
public static Chain GetChain(string chainName)
{
if (string.IsNullOrEmpty(chainName))
throw new Exception("Chain name is null or empty");
return GetChains().FirstOrDefault(c => c.Name == chainName);
}
public static List<Chain> GetChains()
{
var chains = new List<Chain>()
{
GetArbitrum(),
GetEthereum(),
//GetArbitrumGoerli(),
//GetGoerli()
};
return chains;
}
public static Chain GetArbitrum()
{
return new Chain()
{
Name = Constants.Chains.Arbitrum,
RpcUrl = RPC_ARBITRUM
};
}
public static Chain GetEthereum()
{
return new Chain()
{
Name = Constants.Chains.Ethereum,
RpcUrl = RPC_ETHEREUM
};
}
public static Chain GetArbitrumGoerli()
{
return new Chain()
{
Name = Constants.Chains.ArbitrumGoerli,
RpcUrl = RPC_ARBITRUM_GOERLI
};
}
public static Chain GetGoerli()
{
return new Chain()
{
Name = Constants.Chains.Goerli,
RpcUrl = RPC_ETHEREUM_GOERLI
};
}
}

View File

@@ -0,0 +1,139 @@
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Web3;
using System.Numerics;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public static class GmxHelpers
{
public static decimal GetQuantityForLeverage(decimal quantity, decimal? leverage)
{
return leverage.HasValue ? leverage.Value * quantity : quantity;
}
public static (List<string> CollateralTokens, List<string> IndexTokens, List<bool> IsLong) GetPositionQueryData(List<string> contractAddress)
{
var collateralToken = new List<string>();
var indexTokens = new List<string>();
var isLongs = new List<bool>();
foreach (var token in contractAddress)
{
collateralToken.Add(token);
indexTokens.Add(token);
isLongs.Add(true);
}
foreach (var token in contractAddress)
{
collateralToken.Add(Arbitrum.Address.USDC);
indexTokens.Add(token);
isLongs.Add(false);
}
return (collateralToken, indexTokens, isLongs);
}
public static List<BigInteger> GetIndexesRange(int lastIndex)
{
var indexes = new List<BigInteger>();
var limit = 15;
var from = (lastIndex - limit) < 0 ? 0 : lastIndex - limit;
for (int i = from; i <= lastIndex; i++)
{
indexes.Add(new BigInteger(i));
}
return indexes;
}
public static BigInteger GetAcceptablePrice(decimal price, bool isLong)
{
decimal priceBasisPoints;
var basisPointDivisor = 10000m;
var allowedSlippage = 34m;
var toDecimal = 30;
if (isLong)
{
priceBasisPoints = basisPointDivisor - allowedSlippage;
}
else
{
priceBasisPoints = basisPointDivisor + allowedSlippage;
}
var test = Web3.Convert.ToWei(price, toDecimal) * new BigInteger(priceBasisPoints);
var priceLimit = test / Web3.Convert.ToWei(basisPointDivisor, 0);
return priceLimit;
}
internal static BigInteger? GetGasLimit()
{
throw new NotImplementedException();
}
internal static List<Trade> Map(List<GmxOrder> orders, Ticker ticker)
{
return orders.ConvertAll(order => Map(order, ticker));
}
private static Trade Map(GmxOrder order, Ticker ticker)
{
var trade = new Trade(DateTime.UtcNow,
order.IsLong ? TradeDirection.Short : TradeDirection.Long,
TradeStatus.Requested,
GetTradeType(order.IsLong, order.TriggerAboveThreshold),
ticker,
Convert.ToDecimal(order.SizeDelta),
Convert.ToDecimal(order.TriggerPrice),
null,
"", ""
);
return trade;
}
public static bool GetTriggerAboveThreshold(bool isLong, TradeType tradeType)
{
if ((isLong && tradeType == TradeType.TakeProfit) ||
(!isLong && tradeType == TradeType.StopLoss))
{
return true;
}
return false;
}
public static TradeType GetTradeType(bool isLong, bool isAboveThreshold)
{
if ((isLong && isAboveThreshold) ||
(!isLong && !isAboveThreshold))
{
return TradeType.TakeProfit;
}
return TradeType.StopLoss;
}
internal static string GeTimeframe(Timeframe timeframe)
{
return timeframe switch
{
Timeframe.FiveMinutes => "5m",
Timeframe.FifteenMinutes => "15m",
Timeframe.ThirtyMinutes => "30m",
Timeframe.OneHour => "1h",
Timeframe.FourHour => "4h",
Timeframe.OneDay => "1d",
_ => throw new NotImplementedException(),
};
}
}

View File

@@ -0,0 +1,177 @@
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Web3;
using System.Numerics;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public static class GmxMappers
{
internal static Trade Map(GmxPosition? position, Ticker ticker)
{
if (position == null)
{
return null;
}
var leverage = position.SizeDelta / position.Collateral;
var trade = new Trade(
DateHelpers.GetFromUnixTimestamp((int)position.LastIncreasedTime),
position.IsLong ? TradeDirection.Long : TradeDirection.Short,
TradeStatus.Filled,
TradeType.Limit,
ticker,
Web3.Convert.FromWei(position.SizeDelta, 30),
Web3.Convert.FromWei(position.AveragePrice, 30),
(decimal)leverage,
"",
"");
return trade;
}
public static List<GmxPosition> MapPositions(List<BigInteger> positionData,
(List<string> CollateralTokens, List<string> IndexTokens, List<bool> IsLong) queryData)
{
var gmxPositions = new List<GmxPosition>();
var propLength = 9;
for (int i = 0; i < queryData.CollateralTokens.Count; i++)
{
var gmxPosition = new GmxPosition
{
CollateralToken = queryData.CollateralTokens[i],
IndexToken = queryData.IndexTokens[i],
IsLong = queryData.IsLong[i],
SizeDelta = positionData[i * propLength],
Collateral = positionData[i * propLength + 1],
AveragePrice = positionData[i * propLength + 2],
EntryFundingRate = positionData[i * propLength + 3],
//gmxPosition.CumulativeFundingRate = collateralToken.cumulativeFundingRate;
HasRealisedProfit = positionData[i * propLength + 4].Equals(1),
RealisedPnl = positionData[i * propLength + 5],
LastIncreasedTime = positionData[i * propLength + 6],
HasProfit = positionData[i * propLength + 7],
Delta = positionData[i * propLength + 8]
};
if (!gmxPosition.SizeDelta.IsZero)
gmxPositions.Add(gmxPosition);
}
return gmxPositions;
}
public static List<GmxOrder> MapIncrease(
List<BigInteger> orderData,
List<string> addressData,
List<BigInteger> indexes)
{
var extractor = (List<BigInteger> orderProperty, List<string> address) =>
{
var order = new GmxOrder
{
PurchaseToken = address[0].ToString(),
CollateralToken = address[1].ToString(),
IndexToken = address[2].ToString(),
PurchaseTokenAmount = Web3.Convert.FromWeiToBigDecimal(orderProperty[0], 18).ToString(),
SizeDelta = Web3.Convert.FromWeiToBigDecimal(orderProperty[1], 30).ToString(),
IsLong = orderProperty[2].ToString() == "1",
TriggerPrice = Web3.Convert.FromWeiToBigDecimal(orderProperty[3], 30).ToString(),
TriggerAboveThreshold = orderProperty[4].ToString() == "1",
Type = GmxOrderType.Increase
};
return order;
};
return ParseOrdersData(orderData, addressData, extractor, indexes, 5, 3);
}
public static List<GmxOrder> MapDecrease(
List<BigInteger> orderData,
List<string> addressData,
List<BigInteger> indexes)
{
var extractor = (List<BigInteger> orderProperty, List<string> address) =>
{
var order = new GmxOrder
{
CollateralToken = address[0],
IndexToken = address[1],
CollateralDelta = orderProperty[0].ToString(),
SizeDelta = Web3.Convert.FromWeiToBigDecimal(orderProperty[1], 30).ToString(),
IsLong = orderProperty[2].ToString() == "1",
TriggerPrice = Web3.Convert.FromWeiToBigDecimal(orderProperty[3], 30).ToString(),
TriggerAboveThreshold = orderProperty[4].ToString() == "1",
Type = GmxOrderType.Decrease
};
return order;
};
return ParseOrdersData(orderData, addressData, extractor, indexes, 5, 2);
}
public static List<GmxOrder> ParseOrdersData(
List<BigInteger> orderData,
List<string> addressData,
Func<List<BigInteger>, List<string>, GmxOrder> extractor,
List<BigInteger> indexes,
int uintPropsLength,
int addressPropsLength)
{
if (orderData.Count == 0 || addressData.Count == 0)
{
return new List<GmxOrder>();
}
var count = orderData.Count / uintPropsLength;
var orders = new List<GmxOrder>();
for (int i = 0; i < count; i++)
{
var slicedAddress = addressData
.Skip(addressPropsLength * i)
.Take(addressPropsLength)
.ToList();
if (slicedAddress[0] == Arbitrum.Address.Zero && slicedAddress[1] == Arbitrum.Address.Zero)
{
continue;
}
var slicedProperty = orderData
.Skip(uintPropsLength * i)
.Take(uintPropsLength * i)
.ToList();
var order = extractor(slicedProperty, slicedAddress);
order.Index = indexes[i];
orders.Add(order);
}
return orders;
}
internal static Candle Map(GmxOhlc price, Ticker ticker, Timeframe timeframe)
{
return new Candle()
{
Date = DateHelpers.GetFromUnixTimestamp(price.t),
OpenTime = DateHelpers.GetFromUnixTimestamp(price.t).AddSeconds(-1),
Open = Convert.ToDecimal(price.o),
High = Convert.ToDecimal(price.h),
Low = Convert.ToDecimal(price.l),
Close = Convert.ToDecimal(price.c),
Exchange = TradingExchanges.Evm,
Ticker = ticker.ToString(),
Timeframe = timeframe
};
}
}

View File

@@ -0,0 +1,458 @@
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Tools.OrderBook;
using Managing.Tools.OrderBook.ContractDefinition;
using Managing.Tools.OrderBookReader;
using Managing.Tools.OrderBookReader.ContractDefinition;
using Managing.Tools.PositionRouter;
using Managing.Tools.PositionRouter.ContractDefinition;
using Managing.Tools.Reader;
using Managing.Tools.Reader.ContractDefinition;
using Managing.Tools.Router;
using Managing.Tools.Router.ContractDefinition;
using Nethereum.Contracts.Standards.ERC20;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Nethereum.Util;
using Nethereum.Web3;
using System.Numerics;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public static class GmxService
{
private const decimal _orderFeesExecution = 0.0003m;
private const decimal _positionUpdateFees = 0.0001m;
public async static Task InitAccountForTrading(Web3 web3, string publicAddress, List<Ticker> tickers)
{
var router = new RouterService(web3, Arbitrum.Address.Router);
if (!await IsPluginAdded(web3, publicAddress, Arbitrum.Address.PositionRouter))
{
var routerApproval = await router
.ApprovePluginRequestAndWaitForReceiptAsync(Arbitrum.Address.PositionRouter);
}
if (!await IsPluginAdded(web3, publicAddress, Arbitrum.Address.OrderBook))
{
var routerApproval = await router
.ApprovePluginRequestAsync(Arbitrum.Address.OrderBook);
}
foreach (var ticker in tickers)
{
var conntractAddress = TokenService.GetContractAddress(ticker);
await ApproveToken(web3, publicAddress, conntractAddress);
}
}
public async static Task<bool> IsPluginAdded(Web3 web3, string publicAddress, string pluginAddress)
{
var router = new RouterService(web3, Arbitrum.Address.Router);
var function = new ApprovedPluginsFunction
{
ReturnValue1 = publicAddress,
ReturnValue2 = pluginAddress
};
var isAdded = await router.ApprovedPluginsQueryAsync(function);
return isAdded;
}
public async static Task ApproveToken(Web3 web3, string publicAddress, string contractAddress)
{
var input = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction
{
Spender = publicAddress
};
var contract = new ERC20ContractService(web3.Eth, contractAddress);
var approval = await contract.ApproveRequestAsync(input);
}
public static async Task<bool> ApproveOrder(Web3 web3, Ticker ticker, string publicAddress, decimal amount)
{
var contractAddress = TokenService.GetContractAddress(ticker);
var contract = new ERC20ContractService(web3.Eth, contractAddress);
var allowanceQuery = new AllowanceFunction
{
Owner = publicAddress,
Spender = Arbitrum.Address.Router
};
var allowance = await contract.AllowanceQueryAsync(allowanceQuery);
if (allowance.IsZero) return false;
var approveQuery = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction
{
FromAddress = publicAddress,
Spender = Arbitrum.Address.Router,
Value = Web3.Convert.ToWei(amount)
};
var approval = await contract.ApproveRequestAsync(approveQuery);
return true;
}
public async static Task<Trade> IncreasePosition(Web3 web3, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
{
var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage);
var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook);
var contractAddress = TokenService.GetContractAddress(ticker);
var isLong = direction == TradeDirection.Long;
var function = new CreateIncreaseOrderFunction();
// Forcing path to use USDC to pay the trade
function.Path = new List<string> { Arbitrum.Address.USDC };
function.AmountIn = Web3.Convert.ToWei(quantity * price, 6); // Price in $ to pay the long/short. Ex 11.42$
function.IndexToken = contractAddress; // Token to long/short
function.MinOut = new BigInteger(0);
// Size of the position with the leveraged quantity
// Ex : Long 11$ x3. SizeDelta = 33$
function.SizeDelta = Web3.Convert.ToWei(quantityLeveraged * price, UnitConversion.EthUnit.Tether);
function.CollateralToken = Arbitrum.Address.USDC; // USDC
function.IsLong = isLong;
function.TriggerPrice = Web3.Convert.ToWei(price, 30); // Price of the order execution
function.TriggerAboveThreshold = false;
function.ExecutionFee = Web3.Convert.ToWei(_orderFeesExecution); // Fee required to execute tx
function.ShouldWrap = false;
// Specify the tx opts
function.AmountToSend = Web3.Convert.ToWei(_orderFeesExecution);
function.FromAddress = publicAddress;
//function.MaxFeePerGas = await orderBook.ContractHandler.EstimateGasAsync(function);
function.GasPrice = GetGasPrice();
// Approving Router to transfer ERC20 token
var approval = await ApproveOrder(web3, Ticker.USDC, publicAddress, _orderFeesExecution);
if (!approval) return null;
var receipt = await orderBook
.CreateIncreaseOrderRequestAndWaitForReceiptAsync(function);
var trade = new Trade(DateTime.UtcNow,
direction,
TradeStatus.Requested,
TradeType.Limit,
ticker,
quantity,
price,
leverage,
receipt.TransactionHash,
"");
return trade;
}
public async static Task<Trade> DecreasePosition(Web3 web3, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
{
var trade = new Trade(DateTime.UtcNow,
direction,
TradeStatus.Cancelled,
TradeType.Market,
ticker,
quantity,
price,
leverage,
"",
"");
// Check if there is quantity in position
if (await QuantityInPosition(web3, publicAddress, ticker) == 0) return trade;
var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage);
var positionRouter = new PositionRouterService(web3, Arbitrum.Address.PositionRouter);
var contractAddress = TokenService.GetContractAddress(ticker);
var isLong = direction == TradeDirection.Long;
var function = new CreateDecreasePositionFunction();
// Forcing path to use contract address to widthdraw funds
// The address for closing a short, should be USDC
//function.Path = new List<string> { contractAddress };
function.Path = new List<string> { Arbitrum.Address.USDC };
// the index token of the position
function.IndexToken = contractAddress; // Token to long/short
// the amount of collateral in USD value to withdraw
function.CollateralDelta = new BigInteger(0); // Price in $ to pay the long/short. Ex 11.42$
//function.CollateralDelta = Web3.Convert.ToWei(quantity * price, 6); // Price in $ to pay the long/short. Ex 11.42$
// the USD value of the change in position size
function.SizeDelta = Web3.Convert.ToWei(quantity, UnitConversion.EthUnit.Tether);
function.IsLong = isLong;
// the address to receive the withdrawn tokens
function.Receiver = publicAddress;
// the USD value of the min (for longs) or max (for shorts) index price acceptable when executing the request
function.AcceptablePrice = GmxHelpers.GetAcceptablePrice(price, isLong);
// the min output token amount
function.MinOut = new BigInteger(0);
function.ExecutionFee = Web3.Convert.ToWei(_positionUpdateFees); // Fee required to execute tx
function.WithdrawETH = false;
function.CallbackTarget = Arbitrum.Address.Zero;
// Specify the tx opts
function.AmountToSend = Web3.Convert.ToWei(_positionUpdateFees);
function.FromAddress = publicAddress;
function.MaxFeePerGas = await positionRouter.ContractHandler.EstimateGasAsync(function);
function.GasPrice = GetGasPrice();
var approval = await ApproveOrder(web3, ticker, publicAddress, _positionUpdateFees);
if (!approval) return null;
var receipt = await positionRouter
.CreateDecreasePositionRequestAndWaitForReceiptAsync(function);
trade.SetExchangeOrderId(receipt.TransactionHash);
trade.SetStatus(receipt.Status.Value.IsOne ? TradeStatus.Requested : TradeStatus.Cancelled);
return trade;
}
public static async Task<Trade> DecreaseOrder(Web3 web3, TradeType tradeType, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
{
var trade = new Trade(DateTime.UtcNow,
direction,
TradeStatus.Cancelled,
tradeType,
ticker,
quantity,
price,
leverage,
"",
"");
// Check if there is quantity in position
var currentPosition = await GetGmxPosition(web3, publicAddress, ticker);
if (currentPosition == null || currentPosition?.SizeDelta == 0) return trade;
var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage);
var orderbook = new OrderBookService(web3, Arbitrum.Address.OrderBook);
var contractAddress = TokenService.GetContractAddress(ticker);
var isLong = direction != TradeDirection.Long;
var function = new CreateDecreaseOrderFunction();
// the index token of the position
function.IndexToken = contractAddress; // Token to long/short
// the USD value of the change in position size
function.SizeDelta = currentPosition.SizeDelta;
function.CollateralToken = Arbitrum.Address.USDC;
// the amount of collateral in USD value to withdraw
function.CollateralDelta = new BigInteger(0); // Price in $ to pay the long/short. Ex 11.42$
//function.CollateralDelta = Web3.Convert.ToWei(quantity * price, 6); // Price in $ to pay the long/short. Ex 11.42$
function.IsLong = isLong;
// the USD value of the min (for longs) or max (for shorts) index price acceptable when executing the request
function.TriggerPrice = GmxHelpers.GetAcceptablePrice(price, isLong);
function.TriggerAboveThreshold = GmxHelpers.GetTriggerAboveThreshold(isLong, tradeType);
// Specify the tx opts
function.AmountToSend = Web3.Convert.ToWei(_orderFeesExecution);
function.FromAddress = publicAddress;
function.MaxFeePerGas = await orderbook.ContractHandler.EstimateGasAsync(function);
function.GasPrice = GetGasPrice();
var approval = await ApproveOrder(web3, ticker, publicAddress, _positionUpdateFees);
if (!approval) return null;
var receipt = await orderbook
.CreateDecreaseOrderRequestAndWaitForReceiptAsync(function);
trade.SetExchangeOrderId(receipt.TransactionHash);
trade.SetStatus(TradeStatus.Requested);
return trade;
}
public async static Task<bool> CancelOrders(Web3 web3, string publicAddress, Ticker ticker)
{
var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook);
var orders = await GetOrders(web3, publicAddress, ticker);
if (!orders.Any()) return true;
var function = new CancelMultipleFunction();
var increaseOrderIndexes = orders.Where(i => i.Type == GmxOrderType.Increase);
function.IncreaseOrderIndexes = increaseOrderIndexes.Select(o => o.Index).ToList();
var decreaseOrderIndexes = orders.Where(i => i.Type == GmxOrderType.Decrease);
function.DecreaseOrderIndexes = decreaseOrderIndexes.Select(o => o.Index).ToList();
function.SwapOrderIndexes = new List<BigInteger>();
try
{
if (function.DecreaseOrderIndexes.Any() || function.IncreaseOrderIndexes.Any())
{
function.MaxFeePerGas = await orderBook.ContractHandler.EstimateGasAsync(function);
function.GasPrice = GetGasPrice();
var cancellation = await orderBook.CancelMultipleRequestAndWaitForReceiptAsync(function);
}
}
catch (Exception ex)
{
return false;
}
return true;
}
private static BigInteger GetGasPrice()
{
return Web3.Convert.ToWei(0.1, UnitConversion.EthUnit.Gwei);
}
public static async Task<List<GmxOrder>> GetOrders(Web3 web3, string publicAddress, Ticker ticker)
{
var lastIndexes = await GetLastIndex(web3, publicAddress);
var orders = new List<GmxOrder>();
var orderBookReader = new OrderBookReaderService(web3, Arbitrum.Address.OrderBookReader);
var increaseOrders = await GetIncreaseOrders(orderBookReader, publicAddress, lastIndexes.IncreaseIndex);
var decreaseOrders = await GetDecreaseOrders(orderBookReader, publicAddress, lastIndexes.DecreaseIndex);
orders.AddRange(increaseOrders);
orders.AddRange(decreaseOrders);
var contractAddress = TokenService.GetContractAddress(ticker);
var ordersFiltered = orders.Where(o => string.Equals(o.IndexToken, contractAddress, StringComparison.CurrentCultureIgnoreCase));
return ordersFiltered.ToList();
}
private static async Task<List<GmxOrder>> GetIncreaseOrders(OrderBookReaderService orderBookReader, string publicAddress, int lastIndex)
{
var increaseIndex = GmxHelpers.GetIndexesRange(lastIndex);
var increaseOrdersFunction = new GetIncreaseOrdersFunction
{
OrderBookAddress = Arbitrum.Address.OrderBook,
Account = publicAddress,
Indices = increaseIndex,
};
var increaseOrders = await orderBookReader.GetIncreaseOrdersQueryAsync(increaseOrdersFunction);
return GmxMappers.MapIncrease(increaseOrders.ReturnValue1, increaseOrders.ReturnValue2, increaseIndex);
}
private static async Task<List<GmxOrder>> GetDecreaseOrders(OrderBookReaderService orderBookReader, string publicAddress, int lastIndex)
{
var increaseIndex = GmxHelpers.GetIndexesRange(lastIndex);
var increaseOrdersFunction = new GetDecreaseOrdersFunction
{
OrderBookAddress = Arbitrum.Address.OrderBook,
Account = publicAddress,
Indices = increaseIndex,
};
var increaseOrders = await orderBookReader.GetDecreaseOrdersQueryAsync(increaseOrdersFunction);
return GmxMappers.MapDecrease(increaseOrders.ReturnValue1, increaseOrders.ReturnValue2, increaseIndex);
}
public static async Task<GmxOrderIndexes> GetLastIndex(Web3 web3, string publicAddress)
{
var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook);
var increaseFunction = new IncreaseOrdersIndexFunction
{
ReturnValue1 = publicAddress
};
var decreaseFunction = new DecreaseOrdersIndexFunction
{
ReturnValue1 = publicAddress
};
var swapFunction = new SwapOrdersIndexFunction
{
ReturnValue1 = publicAddress
};
var increaseIndex = await orderBook.IncreaseOrdersIndexQueryAsync(increaseFunction);
var decreaseIndex = await orderBook.DecreaseOrdersIndexQueryAsync(decreaseFunction);
var swapIndex = await orderBook.SwapOrdersIndexQueryAsync(swapFunction);
var indexes = new GmxOrderIndexes
{
SwapIndex = (int)swapIndex > 0 ? (int)swapIndex - 1 : (int)swapIndex,
IncreaseIndex = (int)increaseIndex > 0 ? (int)increaseIndex - 1 : (int)increaseIndex,
DecreaseIndex = (int)decreaseIndex > 0 ? (int)decreaseIndex - 1 : (int)decreaseIndex
};
return indexes;
}
public static async Task<Trade> GetTrade(Web3 web3, string publicAddress, Ticker ticker)
{
var position = await GetGmxPosition(web3, publicAddress, ticker);
return GmxMappers.Map(position, ticker);
}
public static async Task<GmxPosition> GetGmxPosition(Web3 web3, string publicAddress, Ticker ticker)
{
var reader = new ReaderService(web3, Arbitrum.Address.Reader);
var contractAddress = TokenService.GetContractAddress(ticker);
var queryData = GmxHelpers.GetPositionQueryData(new List<string> { contractAddress });
var function = new GetPositionsFunction
{
Vault = Arbitrum.Address.Vault,
Account = publicAddress,
CollateralTokens = queryData.CollateralTokens,
IndexTokens = queryData.IndexTokens,
IsLong = queryData.IsLong
};
var result = await reader.GetPositionsQueryAsync(function);
var positions = GmxMappers.MapPositions(result, queryData);
var position = positions.FirstOrDefault(p => p.IndexToken == contractAddress);
return position;
}
public static async Task<decimal> QuantityInPosition(Web3 web3, string key, Ticker ticker)
{
var position = await GetTrade(web3, key, ticker);
return position?.Quantity ?? 0m;
}
public static async Task<decimal> GetFee(Web3 web3, decimal ethPrice)
{
var positionRouter = new PositionRouterService(web3, Arbitrum.Address.PositionRouter);
var contractAddress = TokenService.GetContractAddress(Ticker.BTC);
var function = new CreateDecreasePositionFunction();
function.Path = new List<string> { contractAddress };
function.IndexToken = contractAddress; // Token to long/short
function.CollateralDelta = new BigInteger(0); // Price in $ to pay the long/short. Ex 11.42$
function.SizeDelta = Web3.Convert.ToWei(100, UnitConversion.EthUnit.Tether);
function.IsLong = true;
function.Receiver = Arbitrum.Address.Zero;
function.AcceptablePrice = GmxHelpers.GetAcceptablePrice(100, true);
function.MinOut = new BigInteger(0);
function.ExecutionFee = Web3.Convert.ToWei(_positionUpdateFees); // Fee required to execute tx
function.WithdrawETH = false;
function.CallbackTarget = Arbitrum.Address.Zero;
function.AmountToSend = Web3.Convert.ToWei(_positionUpdateFees);
function.FromAddress = Arbitrum.Address.Zero;
var totalCost = 0m;
try
{
var gasCost = await positionRouter.ContractHandler.EstimateGasAsync(function);
var gasPrice = GetGasPrice();
var gas = gasPrice * gasCost;
totalCost = ethPrice * Web3.Convert.FromWei(gas, 18);
return totalCost;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return totalCost;
}
}

View File

@@ -0,0 +1,45 @@
using Nethereum.Contracts;
using Nethereum.Contracts.Standards.ERC721.ContractDefinition;
using Nethereum.Web3;
namespace Managing.Infrastructure.Evm.Services;
public static class NftService
{
public static async Task<List<EventLog<TransferEventDTO>>> GetNftEvent(Web3 web3, string owner, string contract)
{
try
{
// Retrieve transfer event of the contract
var transferEvent = web3.Eth.GetEvent<TransferEventDTO>(contract);
// Create IN & OUT filter to filter the return transfers changes
var transferFilterIn = transferEvent.CreateFilterInput<string, string>(null, owner);
var transferFilterOut = transferEvent.CreateFilterInput(owner);
// Retrieve changes based on filter
var transferInLogs = await transferEvent.GetAllChangesAsync(transferFilterIn);
var transferOutLogs = await transferEvent.GetAllChangesAsync(transferFilterOut);
var list = new List<EventLog<TransferEventDTO>>();
// For each transfer IN, we add the event into the list
foreach (var ins in transferInLogs)
{
list.Add(ins);
}
// Remove all transfer OUT of the list because the use might already send the token
foreach (var ins in transferOutLogs)
{
list.Remove(ins);
}
return list;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return null;
}
}
}

View File

@@ -0,0 +1,45 @@
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.SystemTextJson;
using Managing.Domain.Evm;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Services;
public static class SubgraphService
{
private const string SUBGRAPH_UNISWAP_V2 = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2";
private const string SUBGRAPH_CHAINLINK = "https://api.thegraph.com/subgraphs/name/openpredict/chainlink-prices-subgraph";
private const string SUBGRAPH_CHAINLINK_GMX = "https://api.thegraph.com/subgraphs/name/deividask/chainlink";
private const string SUBGRAPH_GBC = "https://api.thegraph.com/subgraphs/name/nissoh/gmx-arbitrum";
public static GraphQLHttpClient GetSubgraphClient(SubgraphProvider subgraphProvider)
{
var url = GetSubgraph(subgraphProvider).Url;
var graphQLOptions = new GraphQLHttpClientOptions
{
EndPoint = new Uri(url)
};
return new GraphQLHttpClient(graphQLOptions, new SystemTextJsonSerializer());
}
private static Subgraph GetSubgraph(SubgraphProvider subgraphProvider)
{
return new Subgraph()
{
SubgraphProvider = subgraphProvider,
Url = GetSubgraphUrl(subgraphProvider)
};
}
private static string GetSubgraphUrl(SubgraphProvider subgraphProvider)
{
return subgraphProvider switch
{
SubgraphProvider.UniswapV2 => SUBGRAPH_UNISWAP_V2,
SubgraphProvider.ChainlinkPrice => SUBGRAPH_CHAINLINK,
SubgraphProvider.ChainlinkGmx => SUBGRAPH_CHAINLINK_GMX,
SubgraphProvider.Gbc => SUBGRAPH_GBC,
_ => throw new Exception("No url for subgraphprovider")
};
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,98 @@
using Managing.Application.Abstractions.Services;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models;
using System.Net.Http.Json;
namespace Managing.Infrastructure.Evm.Services;
public class TradaoService : ITradaoService
{
private readonly HttpClient _httpClient;
public TradaoService()
{
_httpClient = new HttpClient(); ;
}
public async Task<List<Trader>> GetBadTrader()
{
var bestTraders = await _httpClient.GetFromJsonAsync<TradaoList>($"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/asc/2592000/0/?current=1&limit=500&order=asc&window=2592000&chain=42161&exchange=gmx&openPosition=0");
if (bestTraders == null || bestTraders.row.Count == 0)
{
return new List<Trader>();
}
return await GetTraderDetails(bestTraders);
}
public async Task<List<Trader>> GetBestTrader()
{
var bestTraders = await _httpClient.GetFromJsonAsync<TradaoList>($"https://api.tradao.xyz/v1/td/dashboard/42161/gmx/pnlTop/500/desc/2592000/0/?current=1&limit=500&order=desc&window=2592000&chain=42161&exchange=gmx&openPosition=0");
if (bestTraders == null || bestTraders.row.Count == 0)
{
return new List<Trader>();
}
return await GetTraderDetails(bestTraders);
}
public async Task<List<Trade>> GetTrades(string address)
{
var response = await _httpClient.GetFromJsonAsync<TradaoUserDetails>($"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{address}");
var trades = new List<Trade>();
if (response == null) return trades;
foreach (var position in response.openPositions)
{
var trade = new Trade(
DateTime.UtcNow,
position.isLong ? Common.Enums.TradeDirection.Long : Common.Enums.TradeDirection.Short,
Common.Enums.TradeStatus.Filled,
Common.Enums.TradeType.Market,
TokenService.GetTicker(position.indexTokenAddress),
Convert.ToDecimal(position.collateral),
Convert.ToDecimal(position.averagePrice),
Convert.ToDecimal(position.position) / Convert.ToDecimal(position.collateral),
address, position.liqPrice
);
trades.Add(trade);
}
return trades;
}
private async Task<List<Trader>> GetTraderDetails(TradaoList traders)
{
var result = new List<Trader>();
foreach (var trader in traders.row)
{
var response = await _httpClient.GetFromJsonAsync<TradaoUserDetails>($"https://api.tradao.xyz/v1/td/trader/42161/gmx/insights/{trader.user}");
if (response != null)
result.Add(Map(response, trader.user));
}
return result;
}
private Trader Map(TradaoUserDetails response, string address)
{
return new Trader
{
Address = address,
Winrate = (int)(response.summary.winRate * 100),
Pnl = Convert.ToDecimal(response.summary.pnl),
TradeCount = response.summary.trades,
AverageWin = Convert.ToDecimal(response.summary.averageWin),
AverageLoss = Convert.ToDecimal(response.summary.averageLoss),
Roi = Convert.ToDecimal(response.summary.roi)
};
}
}

View File

@@ -0,0 +1,122 @@
using GraphQL.Client.Abstractions;
using GraphQL;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Core;
using Managing.Infrastructure.Evm.Subgraphs.Models;
using Managing.Common;
using Managing.Domain.Candles;
using Managing.Infrastructure.Evm.Extensions;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Subgraphs;
public class Chainlink : ISubgraphPrices
{
SubgraphProvider ISubgraphPrices.GetProvider() => SubgraphProvider.ChainlinkPrice;
private readonly IGraphQLClient _graphQLClient;
private readonly string _baseToken = "/USD";
public Chainlink(IGraphQLClient graphQLHttpClient)
{
_graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient));
}
public async Task<IEnumerable<Candle>> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe)
{
var path = ticker.ToString() + _baseToken;
var batchSize = 1000;
var batchMax = 6;
var priceRounds = new List<ChainlinkPrice>();
var feedCondition = $@"{{ assetPair: ""{path}"" }}";
// Fetching prices from graphql ticker
for (int i = 0; i < batchMax; i++)
{
var query = $"{{ prices(first: {batchSize}, skip: {i * batchSize}, orderBy: timestamp, orderDirection: desc, where: {feedCondition} ) {{ timestamp,price}} }}";
var graphQuery = new GraphQLRequest
{
Query = query
};
var response = await _graphQLClient.SendQueryAsync<ChainlinkPrices>(graphQuery);
priceRounds.AddRange(response.Data.Prices);
}
var rounds = new List<Round>();
// Format response
foreach (var round in priceRounds)
{
var timestamp = int.Parse(round.Timestamp);
rounds.Add(new Round
{
UnixTimestamp = timestamp,
Value = (double.Parse(round.Price) / 1e8).ToString(),
Date = DateHelpers.GetFromUnixTimestamp(timestamp)
});
}
rounds.Sort((timeA, timeB) => timeA.UnixTimestamp - timeB.UnixTimestamp);
return rounds.ToArray().GetCandles(timeframe, ticker);
}
public Task<decimal> GetVolume(Ticker ticker)
{
//var query = $"{{ assetPairs() {{ id }} }}";
//var graphQuery = new GraphQLRequest
//{
// Query = query
//};
//var response = await _graphQLClient.SendQueryAsync<AssetPairs>(graphQuery);
throw new NotImplementedException();
}
public async Task<IEnumerable<Ticker>> GetTickers()
{
var batchSize = 100;
var batchMax = 10;
var tickers = new List<Ticker>();
for (int i = 0; i < batchMax; i++)
{
var query = $"{{ assetPairs(first: {batchSize}, skip: {i * batchSize}) {{ id }} }}";
var graphQuery = new GraphQLRequest
{
Query = query
};
var response = await _graphQLClient.SendQueryAsync<ChainlinkAssetPairs>(graphQuery);
if (response.Data?.AssetPairs != null)
{
tickers.AddRange(ParseTickers(response.Data.AssetPairs));
}
}
return tickers;
}
private List<Ticker> ParseTickers(List<AssetPair> pairs)
{
var tickers = new List<Ticker>();
foreach (var pair in pairs)
{
var items = pair.Id.Split('/');
if (items.Length == 2 && items[1] == Constants.Stablecoins.Usd)
{
try
{
var ticker = MiscExtensions.ParseEnum<Ticker>(items[0]);
tickers.Add(ticker);
}
catch (Exception ex) { }
}
}
return tickers;
}
}

View File

@@ -0,0 +1,92 @@
using GraphQL.Client.Abstractions;
using GraphQL;
using Managing.Core;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Subgraphs.Models;
using Managing.Domain.Candles;
using Managing.Infrastructure.Evm.Extensions;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Subgraphs;
public class ChainlinkGmx : ISubgraphPrices
{
SubgraphProvider ISubgraphPrices.GetProvider() => SubgraphProvider.ChainlinkGmx;
private readonly IGraphQLClient _graphQLClient;
private readonly string _baseToken = "_USD";
private Dictionary<string, string> _feeds = new Dictionary<string, string>()
{
{"BTC_USD", "0xae74faa92cb67a95ebcab07358bc222e33a34da7" },
{"ETH_USD", "0x37bc7498f4ff12c19678ee8fe19d713b87f6a9e6" },
{"BNB_USD", "0xc45ebd0f901ba6b2b8c7e70b717778f055ef5e6d" },
{"LINK_USD", "0xdfd03bfc3465107ce570a0397b247f546a42d0fa" },
{"UNI_USD", "0x68577f915131087199fe48913d8b416b3984fd38" },
{"SUSHI_USD", "0x7213536a36094cd8a768a5e45203ec286cba2d74" },
{"AVAX_USD", "0x0fc3657899693648bba4dbd2d8b33b82e875105d" },
{"AAVE_USD", "0xe3f0dede4b499c07e12475087ab1a084b5f93bc0" },
{"YFI_USD", "0x8a4d74003870064d41d4f84940550911fbfccf04" },
{"SPELL_USD", "0x8640b23468815902e011948f3ab173e1e83f9879" },
};
public ChainlinkGmx(IGraphQLClient graphQLHttpClient)
{
_graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient));
}
public async Task<IEnumerable<Candle>> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe)
{
var path = ticker.ToString() + _baseToken;
var feed = _feeds.GetValueOrDefault(path);
var perChunk = 1000;
var totalChunk = 6;
var priceRounds = new List<Round>();
var feedCondition = $@"{{ feed: ""{feed}"" }}";
for (int i = 0; i < totalChunk; i++)
{
var query = $"{{ rounds(first: {perChunk}, skip: {i * perChunk}, orderBy: unixTimestamp, orderDirection: desc, where: {feedCondition} ) {{ unixTimestamp,value}} }}";
var graphQuery = new GraphQLRequest
{
Query = query
};
var response = await _graphQLClient.SendQueryAsync<PriceRoundsChainlinkGmx>(graphQuery);
priceRounds.AddRange(response.Data.Rounds);
}
var rounds = new List<Round>();
var uniqTs = new HashSet<int>();
foreach (var round in priceRounds)
{
if (uniqTs.Contains(round.UnixTimestamp))
{
continue;
}
uniqTs.Add(round.UnixTimestamp);
rounds.Add(new Round
{
UnixTimestamp = round.UnixTimestamp,
Value = (double.Parse(round.Value) / 1e8).ToString(),
Date = DateHelpers.GetFromUnixTimestamp(round.UnixTimestamp)
});
}
rounds.Sort((timeA, timeB) => timeA.UnixTimestamp - timeB.UnixTimestamp);
return rounds.ToArray().GetCandles(timeframe, ticker);
}
public Task<decimal> GetVolume(Ticker ticker)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Ticker>> GetTickers()
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,104 @@
using GraphQL;
using GraphQL.Client.Abstractions;
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs.Models;
using NBitcoin;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Subgraphs;
public class Gbc : ISubgraphPrices
{
private readonly IGraphQLClient _graphQLClient;
public SubgraphProvider GetProvider() => SubgraphProvider.Gbc;
public Gbc(IGraphQLClient graphQLHttpClient)
{
_graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient));
}
public async Task<IEnumerable<Candle>> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe)
{
var batchSize = 1000;
var batchMax = 6;
var priceRounds = new List<GbcPrice>();
var tickerContract = TokenService.GetContractAddress(ticker);
var unixTimeframe = timeframe.GetUnixInterval();
var start = startDate.ToUnixTimestamp();
var end = DateTime.UtcNow.ToUnixTimestamp();
var feedCondition = $@"{{ tokenAddress: ""_{tickerContract}"", interval: ""_{unixTimeframe}"", timestamp_gte: {start}, timestamp_lte: {end} }}";
// Fetching prices from graphql ticker
for (int i = 0; i < batchMax; i++)
{
var query = $"{{ pricefeeds(first: {batchSize}, skip: {i * batchSize}, orderBy: timestamp, orderDirection: desc, where: {feedCondition} ) {{ timestamp,o,h,l,c}} }}";
var graphQuery = new GraphQLRequest
{
Query = query
};
var response = await _graphQLClient.SendQueryAsync<GbcPrices>(graphQuery);
priceRounds.AddRange(response.Data.PriceFeeds);
}
priceRounds.Sort((timeA, timeB) => timeA.Timestamp - timeB.Timestamp);
var candles = new List<Candle>();
var firstRound = priceRounds.FirstOrDefault();
if (firstRound == null)
return candles;
var previousCandle = BuildCandle(firstRound, ticker, timeframe);
// Format response
foreach (var price in priceRounds.Skip(1))
{
var candle = BuildCandle(price, ticker, timeframe);
candle.OpenTime = previousCandle.Date;
candles.Add(candle);
}
return candles;
}
private Candle BuildCandle(GbcPrice ohlc, Ticker ticker, Timeframe timeframe)
{
return new Candle()
{
Date = DateHelpers.GetFromUnixTimestamp(ohlc.Timestamp),
Open = FormatPrice(ohlc.O),
High = FormatPrice(ohlc.H),
Low = FormatPrice(ohlc.L),
Close = FormatPrice(ohlc.C),
Exchange = TradingExchanges.Evm,
Ticker = ticker.ToString(),
Timeframe = timeframe
};
}
private static decimal FormatPrice(string price)
{
return (decimal)(double.Parse(price) / 1e30);
}
public Task<decimal> GetVolume(Ticker ticker)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Ticker>> GetTickers()
{
var tickers = new List<Ticker>() {
Ticker.BTC,
Ticker.LINK,
Ticker.ETH,
Ticker.UNI
};
return Task.FromResult(tickers.AsEnumerable());
}
}

View File

@@ -0,0 +1,15 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class GbcPrices
{
public List<GbcPrice> PriceFeeds { get; set; }
}
public class GbcPrice
{
public int Timestamp { get; set; }
public string O { get; set; }
public string H { get; set; }
public string L { get; set; }
public string C { get; set; }
}

View File

@@ -0,0 +1,11 @@
using Nethereum.Contracts.Standards.ERC20.TokenList;
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class Pair
{
public string ReserveETH { get; set; }
public string ReserveUSD { get; set; }
public Token Token0 { get; set; }
public Token Token1 { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class Pools
{
public List<Pair> Pairs { get; set; }
}

View File

@@ -0,0 +1,17 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class ChainlinkPrice
{
public string Price { get; set; }
public string Timestamp { get; set; }
}
public class ChainlinkAssetPairs
{
public List<AssetPair> AssetPairs { get; set; }
}
public class AssetPair
{
public string Id { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class ChainlinkPrices
{
public List<ChainlinkPrice> Prices { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class Round
{
public int UnixTimestamp { get; set; }
public DateTime Date { get; set; }
public string Value { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class PriceRoundsChainlinkGmx
{
public List<Round> Rounds { get; set; }
}

View File

@@ -0,0 +1,20 @@
using Nethereum.Contracts.Standards.ERC20.TokenList;
using System.Text.Json.Serialization;
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class TokenDetails : Token
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string TradeVolume { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string TradeVolumeUSD { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string TotalSupply { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string TotalLiquidity { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
public class TopTokens
{
public List<TokenDetails> Tokens { get; set; }
}

View File

@@ -0,0 +1,41 @@
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Services;
using Microsoft.Extensions.DependencyInjection;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Subgraphs;
public static class SubgraphExtensions
{
public static void AddUniswapV2(this IServiceCollection services)
{
services.AddSingleton<IUniswap>(ctx =>
{
return new Uniswap(SubgraphService.GetSubgraphClient(SubgraphProvider.UniswapV2));
});
}
public static void AddChainlink(this IServiceCollection services)
{
services.AddSingleton<ISubgraphPrices>(ctx =>
{
return new Chainlink(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkPrice));
});
}
public static void AddGbcFeed(this IServiceCollection services)
{
services.AddSingleton<ISubgraphPrices>(ctx =>
{
return new Gbc(SubgraphService.GetSubgraphClient(SubgraphProvider.Gbc));
});
}
public static void AddChainlinkGmx(this IServiceCollection services)
{
services.AddSingleton<ISubgraphPrices>(ctx =>
{
return new ChainlinkGmx(SubgraphService.GetSubgraphClient(SubgraphProvider.ChainlinkGmx));
});
}
}

View File

@@ -0,0 +1,85 @@
using GraphQL.Client.Abstractions;
using GraphQL;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Subgraphs.Models;
using Managing.Domain.Candles;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Subgraphs;
public class Uniswap : IUniswap
{
SubgraphProvider ISubgraphPrices.GetProvider() => SubgraphProvider.UniswapV2;
private readonly IGraphQLClient _graphQLClient;
public Uniswap(IGraphQLClient graphQLHttpClient)
{
_graphQLClient = graphQLHttpClient ?? throw new ArgumentNullException(nameof(graphQLHttpClient));
}
/// <summary>
/// Get the first 150 most liquid market pairs ordered by desc
/// </summary>
/// <returns></returns>
public async Task<Pools> GetMostLiquidMarketPairs()
{
var query = new GraphQLRequest
{
Query = @"
{
pairs(first: 150, orderBy: reserveETH orderDirection: desc){
token0 {
symbol
}
token1 {
symbol
}
reserveETH
reserveUSD
}
}
"
};
GraphQLResponse<Pools> response = await _graphQLClient.SendQueryAsync<Pools>(query);
return response.Data;
}
public async Task<TopTokens> GetTopTokens()
{
var query = new GraphQLRequest
{
Query = @"
{
tokens (first: 150, orderBy: tradeVolumeUSD orderDirection: desc){
symbol
name
tradeVolume
tradeVolumeUSD
totalSupply
totalLiquidity
}
}
"
};
GraphQLResponse<TopTokens> response = await _graphQLClient.SendQueryAsync<TopTokens>(query);
return response.Data;
}
public Task<IEnumerable<Candle>> GetPrices(Ticker ticker, DateTime startDate, Timeframe timeframe)
{
throw new NotImplementedException();
}
public Task<decimal> GetVolume(Ticker ticker)
{
throw new NotImplementedException();
}
public Task<IEnumerable<Ticker>> GetTickers()
{
throw new NotImplementedException();
}
}