docker files fixes from liaqat
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
620
src/Managing.Infrastructure.Web3/EvmManager.cs
Normal file
620
src/Managing.Infrastructure.Web3/EvmManager.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
7
src/Managing.Infrastructure.Web3/Models/GeckoToken.cs
Normal file
7
src/Managing.Infrastructure.Web3/Models/GeckoToken.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Managing.Domain.Evm;
|
||||
|
||||
public class GeckoToken
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Symbol { get; set; }
|
||||
}
|
||||
19
src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrder.cs
Normal file
19
src/Managing.Infrastructure.Web3/Models/Gmx/GmxOrder.cs
Normal 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; }
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
19
src/Managing.Infrastructure.Web3/Models/Gmx/GmxPosition.cs
Normal file
19
src/Managing.Infrastructure.Web3/Models/Gmx/GmxPosition.cs
Normal 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; }
|
||||
}
|
||||
19
src/Managing.Infrastructure.Web3/Models/Gmx/GmxPrices.cs
Normal file
19
src/Managing.Infrastructure.Web3/Models/Gmx/GmxPrices.cs
Normal 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; }
|
||||
}
|
||||
20
src/Managing.Infrastructure.Web3/Models/TradaoList.cs
Normal file
20
src/Managing.Infrastructure.Web3/Models/TradaoList.cs
Normal 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; }
|
||||
|
||||
}
|
||||
49
src/Managing.Infrastructure.Web3/Models/TradaoUserDetails.cs
Normal file
49
src/Managing.Infrastructure.Web3/Models/TradaoUserDetails.cs
Normal 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; }
|
||||
|
||||
}
|
||||
57
src/Managing.Infrastructure.Web3/Referentials/Arbitrum.cs
Normal file
57
src/Managing.Infrastructure.Web3/Referentials/Arbitrum.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
70
src/Managing.Infrastructure.Web3/Services/ChainService.cs
Normal file
70
src/Managing.Infrastructure.Web3/Services/ChainService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
139
src/Managing.Infrastructure.Web3/Services/Gmx/GmxHelpers.cs
Normal file
139
src/Managing.Infrastructure.Web3/Services/Gmx/GmxHelpers.cs
Normal 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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
177
src/Managing.Infrastructure.Web3/Services/Gmx/GmxMappers.cs
Normal file
177
src/Managing.Infrastructure.Web3/Services/Gmx/GmxMappers.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
458
src/Managing.Infrastructure.Web3/Services/Gmx/GmxService.cs
Normal file
458
src/Managing.Infrastructure.Web3/Services/Gmx/GmxService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
45
src/Managing.Infrastructure.Web3/Services/NftService.cs
Normal file
45
src/Managing.Infrastructure.Web3/Services/NftService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/Managing.Infrastructure.Web3/Services/SubgraphService.cs
Normal file
45
src/Managing.Infrastructure.Web3/Services/SubgraphService.cs
Normal 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")
|
||||
};
|
||||
}
|
||||
}
|
||||
65
src/Managing.Infrastructure.Web3/Services/TokenService.cs
Normal file
65
src/Managing.Infrastructure.Web3/Services/TokenService.cs
Normal file
File diff suppressed because one or more lines are too long
98
src/Managing.Infrastructure.Web3/Services/TradaoService.cs
Normal file
98
src/Managing.Infrastructure.Web3/Services/TradaoService.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
122
src/Managing.Infrastructure.Web3/Subgraphs/Chainlink.cs
Normal file
122
src/Managing.Infrastructure.Web3/Subgraphs/Chainlink.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
92
src/Managing.Infrastructure.Web3/Subgraphs/ChainlinkGmx.cs
Normal file
92
src/Managing.Infrastructure.Web3/Subgraphs/ChainlinkGmx.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
104
src/Managing.Infrastructure.Web3/Subgraphs/Gbc.cs
Normal file
104
src/Managing.Infrastructure.Web3/Subgraphs/Gbc.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
11
src/Managing.Infrastructure.Web3/Subgraphs/Models/Pair.cs
Normal file
11
src/Managing.Infrastructure.Web3/Subgraphs/Models/Pair.cs
Normal 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; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
|
||||
|
||||
public class Pools
|
||||
{
|
||||
public List<Pair> Pairs { get; set; }
|
||||
}
|
||||
17
src/Managing.Infrastructure.Web3/Subgraphs/Models/Price.cs
Normal file
17
src/Managing.Infrastructure.Web3/Subgraphs/Models/Price.cs
Normal 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; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
|
||||
|
||||
public class ChainlinkPrices
|
||||
{
|
||||
public List<ChainlinkPrice> Prices { get; set; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
|
||||
|
||||
public class PriceRoundsChainlinkGmx
|
||||
{
|
||||
public List<Round> Rounds { get; set; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Managing.Infrastructure.Evm.Subgraphs.Models;
|
||||
|
||||
public class TopTokens
|
||||
{
|
||||
public List<TokenDetails> Tokens { get; set; }
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
85
src/Managing.Infrastructure.Web3/Subgraphs/Uniswap.cs
Normal file
85
src/Managing.Infrastructure.Web3/Subgraphs/Uniswap.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user