Gmx v2 - Funding rates (#6)

* Setup GMX v2

* Add get markets

* Map token with service

* Add get market info data

* Add get markets

* Add get market token prices

* Get markets infos multicall

* Try call datastore

* Add some tests to figure out why datastore call dont work

* Update funding rates

* clean
This commit is contained in:
Oda
2024-08-17 06:50:18 +07:00
committed by GitHub
parent b4087753c7
commit 68aa7fff5d
75 changed files with 8979 additions and 608 deletions

View File

@@ -37,7 +37,8 @@ public static class ChainService
return new Chain()
{
Name = Constants.Chains.Arbitrum,
RpcUrl = RPC_ARBITRUM
RpcUrl = RPC_ARBITRUM,
ChainId = 42161
};
}
@@ -46,7 +47,8 @@ public static class ChainService
return new Chain()
{
Name = Constants.Chains.Ethereum,
RpcUrl = RPC_ETHEREUM
RpcUrl = RPC_ETHEREUM,
ChainId = 1
};
}
@@ -67,4 +69,4 @@ public static class ChainService
RpcUrl = RPC_ETHEREUM_GOERLI
};
}
}
}

View File

@@ -1,21 +1,26 @@
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Web3;
using System.Globalization;
using System.Numerics;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Core;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Web3;
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)
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>();
@@ -68,6 +73,7 @@ public static class GmxHelpers
{
priceBasisPoints = basisPointDivisor + allowedSlippage;
}
var test = Web3.Convert.ToWei(price, toDecimal) * new BigInteger(priceBasisPoints);
var priceLimit = test / Web3.Convert.ToWei(basisPointDivisor, 0);
@@ -83,20 +89,20 @@ public static class GmxHelpers
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),
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),
Convert.ToDecimal(order.TriggerPrice),
null,
"", ""
);
);
return trade;
}
@@ -136,4 +142,119 @@ public static class GmxHelpers
_ => throw new NotImplementedException(),
};
}
}
public static string GetRandomAddress()
{
var ecKey = Nethereum.Signer.EthECKey.GenerateKey();
var privateKey = ecKey.GetPrivateKeyAsBytes().ToHex();
var account = new Nethereum.Signer.EthECKey(privateKey).GetPublicAddress();
return account;
}
internal static MarketPrices GetContractMarketPrices(List<GmxTokenData> tokens, GmxMarket market)
{
var indexToken = tokens.First(t => GmxV2Helpers.SameAddress(t.Address, market.IndexToken));
var longToken = tokens.First(t => GmxV2Helpers.SameAddress(t.Address, market.LongToken));
var shortToken = tokens.First(t => GmxV2Helpers.SameAddress(t.Address, market.ShortToken));
return new MarketPrices()
{
IndexTokenPrice = ConvertToContractTokenPrice(indexToken.Price, indexToken.Decimals),
LongTokenPrice = ConvertToContractTokenPrice(longToken.Price, longToken.Decimals),
ShortTokenPrice = ConvertToContractTokenPrice(shortToken.Price, shortToken.Decimals)
};
}
private static MarketPrice ConvertToContractTokenPrice(MarketPrice marketPrice, int tokenDecimals)
{
var price = new MarketPrice();
price.Min = ConvertToContractPrice(tokenDecimals, marketPrice.Min);
price.Max = ConvertToContractPrice(tokenDecimals, marketPrice.Max);
return price;
}
private static BigInteger ConvertToContractPrice(decimal price, BigInteger tokenDecimals)
{
// Convert the decimal price to a BigInteger, scaling it up to maintain precision.
BigInteger scaledPrice = new BigInteger(price * (decimal)Math.Pow(10, (double)tokenDecimals));
// Return the scaled price directly since it's already adjusted for token decimals.
return scaledPrice;
}
public static Ticker GetTicker(string marketName)
{
try
{
var indexToken = marketName.Split(' ')[0];
var ticker = MiscExtensions.ParseEnum<Ticker>(indexToken);
return ticker;
}
catch (Exception e)
{
return Ticker.Unknown;
}
}
public static decimal FormatAmount(BigInteger? amount, int tokenDecimals, int? displayDecimals = null,
bool useCommas = false, string defaultValue = "...")
{
if (amount == null || amount == BigInteger.Zero)
{
return 0;
}
if (displayDecimals == null)
{
displayDecimals = 4;
}
var amountStr = Web3.Convert.FromWei(amount.Value, tokenDecimals).ToString(CultureInfo.InvariantCulture);
amountStr = LimitDecimals(amountStr, displayDecimals.Value);
if (displayDecimals != 0)
{
amountStr = PadDecimals(amountStr, displayDecimals.Value);
}
if (useCommas)
{
amountStr = NumberWithCommas(amountStr);
}
return decimal.Parse(amountStr, CultureInfo.InvariantCulture);
}
private static string LimitDecimals(string amountStr, int displayDecimals)
{
var parts = amountStr.Split('.');
if (parts.Length == 2 && parts[1].Length > displayDecimals)
{
parts[1] = parts[1].Substring(0, displayDecimals);
}
return string.Join(".", parts);
}
private static string PadDecimals(string amountStr, int displayDecimals)
{
var parts = amountStr.Split('.');
if (parts.Length == 1)
{
return amountStr + "." + new string('0', displayDecimals);
}
else if (parts.Length == 2)
{
return parts[0] + "." + parts[1].PadRight(displayDecimals, '0');
}
return amountStr;
}
private static string NumberWithCommas(string amountStr)
{
var parts = amountStr.Split('.');
parts[0] = int.Parse(parts[0]).ToString("N0", CultureInfo.InvariantCulture);
return string.Join(".", parts);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,12 @@
using Managing.Core;
using System.Numerics;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Web3;
using System.Numerics;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Evm.Services.Gmx;
@@ -174,4 +176,73 @@ public static class GmxMappers
Timeframe = timeframe
};
}
}
internal static GmxMarket Map(MarketInfo infos)
{
var indexToken =
TokenV2Service.GetTokenByAddress(TokenV2Service.ConvertTokenAddress(infos.Market.IndexToken, "native"));
var longToken = TokenV2Service.GetTokenByAddress(infos.Market.LongToken);
var shortToken = TokenV2Service.GetTokenByAddress(infos.Market.ShortToken);
var isSameCollaterals = infos.Market.LongToken == infos.Market.ShortToken;
var isSpotOnly = infos.Market.IndexToken == Nethereum.Util.AddressUtil.ZERO_ADDRESS;
var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
isSpotOnly);
var symbol = indexToken.Symbol;
return new GmxMarket
{
MarketToken = infos.Market.MarketToken,
IndexToken = infos.Market.IndexToken,
LongToken = infos.Market.LongToken,
ShortToken = infos.Market.ShortToken,
Name = name,
Symbol = symbol,
IsSpotOnly = isSpotOnly,
IsSameCollaterals = isSameCollaterals,
FundingFactorPerSecond = infos.NextFunding.FundingFactorPerSecond,
LongsPayShorts = infos.NextFunding.LongsPayShorts
};
}
public static GmxMarket MapInfos(MarketsProps infos)
{
var indexToken =
TokenV2Service.GetTokenByAddress(TokenV2Service.ConvertTokenAddress(infos.IndexToken, "native"));
var longToken = TokenV2Service.GetTokenByAddress(infos.LongToken);
var shortToken = TokenV2Service.GetTokenByAddress(infos.ShortToken);
var isSameCollaterals = infos.LongToken == infos.ShortToken;
var isSpotOnly = infos.IndexToken == Nethereum.Util.AddressUtil.ZERO_ADDRESS;
var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
isSpotOnly);
return new GmxMarket
{
MarketToken = infos.MarketToken,
IndexToken = infos.IndexToken,
LongToken = infos.LongToken,
ShortToken = infos.ShortToken,
Name = name,
Symbol = indexToken.Symbol,
IsSpotOnly = isSpotOnly,
IsSameCollaterals = isSameCollaterals,
};
}
public static GmxMarketTokenPrice Map(GetMarketTokenPriceOutputDTO response)
{
return new GmxMarketTokenPrice
{
LongTokenAmount = response.ReturnValue2.LongTokenAmount,
ShortTokenAmount = response.ReturnValue2.ShortTokenAmount,
LongTokenUsd = response.ReturnValue2.LongTokenUsd,
ShortTokenUsd = response.ReturnValue2.ShortTokenUsd,
PoolValue = response.ReturnValue2.PoolValue,
LongPnl = response.ReturnValue2.LongPnl,
ShortPnl = response.ReturnValue2.ShortPnl,
NetPnl = response.ReturnValue2.NetPnl,
TotalBorrowingFees = response.ReturnValue2.TotalBorrowingFees,
BorrowingFeePoolFactor = response.ReturnValue2.BorrowingFeePoolFactor,
ImpactPoolAmount = response.ReturnValue2.ImpactPoolAmount
};
}
}

View File

@@ -1,21 +1,21 @@
using Managing.Domain.Trades;
using System.Numerics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Tools.ABI.Reader;
using Managing.Tools.ABI.Reader.ContractDefinition;
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;
@@ -98,7 +98,8 @@ public static class GmxService
return true;
}
public async static Task<Trade> IncreasePosition(Web3 web3, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
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);
@@ -114,9 +115,9 @@ public static class GmxService
// 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.CollateralToken = Arbitrum.Address.USDC; // USDC
function.IsLong = isLong;
function.TriggerPrice = Web3.Convert.ToWei(price, 30); // Price of the order execution
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;
@@ -149,18 +150,19 @@ public static class GmxService
return trade;
}
public async static Task<Trade> DecreasePosition(Web3 web3, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
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,
"",
"");
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;
@@ -212,18 +214,19 @@ public static class GmxService
return trade;
}
public static async Task<Trade> DecreaseOrder(Web3 web3, TradeType tradeType, string publicAddress, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
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,
"",
"");
direction,
TradeStatus.Cancelled,
tradeType,
ticker,
quantity,
price,
leverage,
"",
"");
// Check if there is quantity in position
var currentPosition = await GetGmxPosition(web3, publicAddress, ticker);
@@ -318,12 +321,14 @@ public static class GmxService
orders.AddRange(increaseOrders);
orders.AddRange(decreaseOrders);
var contractAddress = TokenService.GetContractAddress(ticker);
var ordersFiltered = orders.Where(o => string.Equals(o.IndexToken, contractAddress, StringComparison.CurrentCultureIgnoreCase));
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)
private static async Task<List<GmxOrder>> GetIncreaseOrders(OrderBookReaderService orderBookReader,
string publicAddress, int lastIndex)
{
var increaseIndex = GmxHelpers.GetIndexesRange(lastIndex);
var increaseOrdersFunction = new GetIncreaseOrdersFunction
@@ -338,7 +343,8 @@ public static class GmxService
return GmxMappers.MapIncrease(increaseOrders.ReturnValue1, increaseOrders.ReturnValue2, increaseIndex);
}
private static async Task<List<GmxOrder>> GetDecreaseOrders(OrderBookReaderService orderBookReader, string publicAddress, int lastIndex)
private static async Task<List<GmxOrder>> GetDecreaseOrders(OrderBookReaderService orderBookReader,
string publicAddress, int lastIndex)
{
var increaseIndex = GmxHelpers.GetIndexesRange(lastIndex);
var increaseOrdersFunction = new GetDecreaseOrdersFunction
@@ -452,7 +458,7 @@ public static class GmxService
{
Console.WriteLine(ex);
}
return totalCost;
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Numerics;
using Microsoft.Extensions.Caching.Memory;
using Nethereum.ABI;
using Nethereum.Util;
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
public class GmxV2Helpers
{
private readonly MemoryCache _dataCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 10000 });
private readonly MemoryCache _stringCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 10000 });
private readonly ABIEncode _abiEncode = new ABIEncode();
private readonly MemoryCacheEntryOptions _absoluteExpiration = new MemoryCacheEntryOptions { Size = 1 };
public byte[] HashData(string[] dataTypes, object[] dataValues)
{
var key = GenerateKey(dataTypes, dataValues);
if (_dataCache.TryGetValue(key, out byte[] cachedHash))
{
return cachedHash;
}
var abiValues = new List<ABIValue>();
foreach (var dataType in dataTypes)
{
var abiValue = new ABIValue(dataType, dataValues[dataTypes.ToList().IndexOf(dataType)]);
abiValues.Add(abiValue);
}
var abiEncode = new ABIEncode();
var encoded = abiEncode.GetABIEncoded(abiValues.ToArray());
var hash = new Sha3Keccack().CalculateHash(encoded);
_dataCache.Set(key, hash, _absoluteExpiration);
return hash;
}
public byte[] HashString(string input)
{
if (_stringCache.TryGetValue(input, out byte[] cachedHash))
{
return cachedHash;
}
// Adjusted to directly work with byte arrays
var hash = HashData(new[] { "string" }, new string[] { input });
_stringCache.Set(input, hash, _absoluteExpiration);
return hash;
}
private string GenerateKey(string[] dataTypes, object[] dataValues)
{
var combined = new List<string>();
for (int i = 0; i < dataTypes.Length; i++)
{
combined.Add($"{dataTypes[i]}:{(dataValues[i] is BigInteger bigInt ? bigInt.ToString() : dataValues[i])}");
}
return string.Join(",", combined);
}
public static bool SameAddress(string address, string address2)
{
return address.ToLower() == address2.ToLower();
}
}

View File

@@ -0,0 +1,531 @@
using System.Numerics;
using Managing.ABI.GmxV2.DataStore;
using Managing.ABI.GmxV2.DataStore.ContractDefinition;
using Managing.ABI.GmxV2.Multicall3.ContractDefinition;
using Managing.ABI.GmxV2.Reader;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Common;
using Managing.Domain.Statistics;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Infrastructure.Evm.Services.Gmx;
using Nethereum.Contracts;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Web3;
public class GmxV2Service
{
private readonly GmxV2Helpers _helpers = new GmxV2Helpers();
public GmxV2Service()
{
}
public async Task<object> GetMarkets(Web3 web3)
{
var reader = new ReaderService(web3, Arbitrum.AddressV2.Reader);
var result = await reader.GetMarketsQueryAsync(Arbitrum.AddressV2.DataStore, 0, 100);
return result;
}
public async Task<List<GmxMarket>> GetMarketsAsync(Web3 web3)
{
var getMarketCallData = new GetMarketsFunction
{
DataStore = Arbitrum.AddressV2.DataStore,
Start = 0,
End = 100
}.GetCallData();
var call1 = new Call
{
Target = Arbitrum.AddressV2.Reader,
CallData = getMarketCallData
};
var aggregateFunction = new AggregateFunction();
aggregateFunction.Calls = new List<Call>();
aggregateFunction.Calls.Add(call1);
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var returnCalls = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(aggregateFunction, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var markets = new GetMarketsOutputDTO().DecodeOutput(returnCalls.ReturnData[0].ToHex());
var result = new List<GmxMarket>();
foreach (var market in markets.ReturnValue1)
{
try
{
result.Add(GmxMappers.MapInfos(market));
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
return result;
}
public async Task<GmxMarketInfo> GetMarketInfo(Web3 web3)
{
var reader = new ReaderService(web3, Arbitrum.AddressV2.Reader);
var market = (await GetMarketsAsync(web3)).First();
var tokensData = await GetTokensData(web3);
var marketPrices = GmxHelpers.GetContractMarketPrices(tokensData, market);
var response =
await reader.GetMarketInfoQueryAsync(Arbitrum.AddressV2.DataStore, marketPrices, market.MarketToken);
// Get hashed key to call datastore
var marketKeys = GmxKeysService.GetMarketKeys(market.MarketToken);
var longInterestUsingLongTokenCallData = new GetUintFunction()
{
Key = marketKeys.LongInterestUsingLongToken.HexToByteArray()
}.GetCallData();
var longInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingLongTokenCallData
};
var longInterestUsingShortTokenCallData = new GetUintFunction()
{
Key = marketKeys.LongInterestUsingShortToken.HexToByteArray()
}.GetCallData();
var longInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingShortTokenCallData
};
var shortInterestUsingLongTokenCallData = new GetUintFunction()
{
Key = marketKeys.ShortInterestUsingLongToken.HexToByteArray()
}.GetCallData();
var shortInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingLongTokenCallData
};
var shortInterestUsingShortTokenCallData = new GetUintFunction()
{
Key = marketKeys.ShortInterestUsingShortToken.HexToByteArray()
}.GetCallData();
var shortInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingShortTokenCallData
};
var dataStoreMulticall = new AggregateFunction();
dataStoreMulticall.Calls = new List<Call>();
dataStoreMulticall.Calls.Add(longInterestUsingLongTokenCall);
dataStoreMulticall.Calls.Add(longInterestUsingShortTokenCall);
dataStoreMulticall.Calls.Add(shortInterestUsingLongTokenCall);
dataStoreMulticall.Calls.Add(shortInterestUsingShortTokenCall);
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var returnCalls = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(dataStoreMulticall, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var marketInfos = GmxMappers.Map(response.ReturnValue1);
var result = new GmxMarketInfo()
{
Market = marketInfos,
Infos = DecodeFundingRateOutput(returnCalls, marketInfos.IsSameCollaterals)
};
return result;
}
public async Task<GmxMarketTokenPrice> GetMarketTokenPrice(Web3 web3, bool maximize = true)
{
var reader = new ReaderService(web3, Arbitrum.AddressV2.Reader);
var market = (await GetMarketsAsync(web3)).First();
var tokensData = await GetTokensData(web3);
var marketPrices = GmxHelpers.GetContractMarketPrices(tokensData, market);
var marketProps = new MarketsProps()
{
MarketToken = market.MarketToken,
IndexToken = market.IndexToken,
LongToken = market.LongToken,
ShortToken = market.ShortToken
};
var response = await reader.GetMarketTokenPriceQueryAsync(Arbitrum.AddressV2.DataStore, marketProps,
marketPrices.IndexTokenPrice, marketPrices.LongTokenPrice, marketPrices.ShortTokenPrice,
_helpers.HashString(Constants.GMX.MAX_PNL_FACTOR_FOR_TRADERS), maximize);
return GmxMappers.Map(response);
}
public async Task<bool> GetIsMarketDisabled(Web3 web3)
{
var dataStoreService = new DataStoreService(web3, Arbitrum.AddressV2.DataStore);
var market = (await GetMarketsAsync(web3)).First();
var hashString = _helpers.HashString(Constants.GMX.MARKET_DISABLED_KEY);
var response = await dataStoreService.GetBoolQueryAsync(_helpers.HashData(
new[] { "bytes32", "address" },
new object[] { hashString, market.MarketToken }));
return response;
}
public async Task<BigInteger> GetLongInterestAmount(Web3 web3)
{
var dataStoreService = new DataStoreService(web3, Arbitrum.AddressV2.DataStore);
var market = (await GetMarketsAsync(web3)).First();
var keys = GmxKeysService.GetMarketKeys(market.MarketToken);
var response = await dataStoreService.GetUintQueryAsync(keys.LongInterestUsingLongToken.HexToByteArray());
return response;
}
public async Task<List<GmxMarketInfo>> GetMarketInfosAsync(Web3 web3)
{
var markets = await GetMarketsAsync(web3);
var readerResult = new List<GmxMarketInfo>();
foreach (var market in markets)
{
var tokensData = await GetTokensData(web3);
var marketPrices = GmxHelpers.GetContractMarketPrices(tokensData, market);
var marketProps = new MarketsProps()
{
MarketToken = market.MarketToken,
IndexToken = market.IndexToken,
LongToken = market.LongToken,
ShortToken = market.ShortToken
};
var getMarketInfoCallData = new GetMarketInfoFunction
{
DataStore = Arbitrum.AddressV2.DataStore,
Prices = marketPrices,
MarketKey = market.MarketToken
}.GetCallData();
var getMarketInfoCall = new Call
{
Target = Arbitrum.AddressV2.Reader,
CallData = getMarketInfoCallData
};
var getMarketTokenPriceCallData = new GetMarketTokenPriceFunction
{
DataStore = Arbitrum.AddressV2.DataStore,
Market = marketProps,
IndexTokenPrice = marketPrices.IndexTokenPrice,
LongTokenPrice = marketPrices.LongTokenPrice,
ShortTokenPrice = marketPrices.ShortTokenPrice,
PnlFactorType = _helpers.HashString(Constants.GMX.MAX_PNL_FACTOR_FOR_TRADERS),
Maximize = true
}.GetCallData();
var getMarketTokenPriceCall = new Call
{
Target = Arbitrum.AddressV2.Reader,
CallData = getMarketTokenPriceCallData
};
var getMarketTokenPriceMinCallData = new GetMarketTokenPriceFunction
{
DataStore = Arbitrum.AddressV2.DataStore,
Market = marketProps,
IndexTokenPrice = marketPrices.IndexTokenPrice,
LongTokenPrice = marketPrices.LongTokenPrice,
ShortTokenPrice = marketPrices.ShortTokenPrice,
PnlFactorType = _helpers.HashString(Constants.GMX.MAX_PNL_FACTOR_FOR_TRADERS),
Maximize = false
}.GetCallData();
var getMarketTokenPriceMinCall = new Call
{
Target = Arbitrum.AddressV2.Reader,
CallData = getMarketTokenPriceMinCallData
};
var readerMulticall = new AggregateFunction();
readerMulticall.Calls = new List<Call>();
readerMulticall.Calls.Add(getMarketInfoCall);
readerMulticall.Calls.Add(getMarketTokenPriceCall);
readerMulticall.Calls.Add(getMarketTokenPriceMinCall);
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var readerCallResults = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(readerMulticall, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var marketInfo = new GetMarketInfoOutputDTO().DecodeOutput(readerCallResults.ReturnData[0].ToHex());
var marketTokenPriceMax =
new GetMarketTokenPriceOutputDTO().DecodeOutput(readerCallResults.ReturnData[1].ToHex());
var marketTokenPriceMin =
new GetMarketTokenPriceOutputDTO().DecodeOutput(readerCallResults.ReturnData[2].ToHex());
// Get hashed key to call datastore
var marketKeys = GmxKeysService.GetMarketKeys(market.MarketToken);
if (marketKeys == null)
continue;
var longInterestUsingLongTokenCallData = new GetUintFunction()
{
Key = marketKeys.LongInterestUsingLongToken.HexToByteArray()
}.GetCallData();
var longInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingLongTokenCallData
};
var longInterestUsingShortTokenCallData = new GetUintFunction()
{
Key = marketKeys.LongInterestUsingShortToken.HexToByteArray()
}.GetCallData();
var longInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingShortTokenCallData
};
var shortInterestUsingLongTokenCallData = new GetUintFunction()
{
Key = marketKeys.ShortInterestUsingLongToken.HexToByteArray()
}.GetCallData();
var shortInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingLongTokenCallData
};
var shortInterestUsingShortTokenCallData = new GetUintFunction()
{
Key = marketKeys.ShortInterestUsingShortToken.HexToByteArray()
}.GetCallData();
var shortInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingShortTokenCallData
};
var dataStoreMulticall = new AggregateFunction();
dataStoreMulticall.Calls = new List<Call>();
dataStoreMulticall.Calls.Add(longInterestUsingLongTokenCall);
dataStoreMulticall.Calls.Add(longInterestUsingShortTokenCall);
dataStoreMulticall.Calls.Add(shortInterestUsingLongTokenCall);
dataStoreMulticall.Calls.Add(shortInterestUsingShortTokenCall);
// Call datastore to get market info
var dataStoreQueryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var dataStoreCallResults = await dataStoreQueryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(dataStoreMulticall, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var marketInfos = GmxMappers.Map(marketInfo.ReturnValue1);
readerResult.Add(new GmxMarketInfo()
{
Market = marketInfos,
Infos = DecodeFundingRateOutput(dataStoreCallResults, marketInfos.IsSameCollaterals),
MarketTokenPriceMax = GmxMappers.Map(marketTokenPriceMax),
MarketTokenPriceMin = GmxMappers.Map(marketTokenPriceMin)
});
}
return readerResult;
}
private GmxMarketInfos DecodeFundingRateOutput(AggregateOutputDTO results, bool isSameCollaterals)
{
var marketDivisor = new BigInteger(isSameCollaterals ? 2 : 1);
var longInterestUsingLongToken =
new GetUintOutputDTO().DecodeOutput(results.ReturnData[0].ToHex()).ReturnValue1 / marketDivisor;
var longInterestUsingShortToken =
new GetUintOutputDTO().DecodeOutput(results.ReturnData[1].ToHex()).ReturnValue1 / marketDivisor;
var shortInterestUsingLongToken =
new GetUintOutputDTO().DecodeOutput(results.ReturnData[2].ToHex()).ReturnValue1 / marketDivisor;
var shortInterestUsingShortToken =
new GetUintOutputDTO().DecodeOutput(results.ReturnData[3].ToHex()).ReturnValue1 / marketDivisor;
var longInterestUsd = longInterestUsingLongToken + longInterestUsingShortToken;
var shortInterestUsd = shortInterestUsingLongToken + shortInterestUsingShortToken;
var marketInfos = new GmxMarketInfos()
{
LongInterestUsd = longInterestUsd,
ShortInterestUsd = shortInterestUsd
};
return marketInfos;
}
public async Task<List<GmxTokenData>> GetTokensData(Web3 web3)
{
var tokens = TokenV2Service.GetTokens().ToList();
var balances = await GetTokenBalances(web3, tokens);
var prices = GetTokenPrices(web3, tokens);
var result = new List<GmxTokenData>();
foreach (var token in tokens)
{
BigInteger? balance = null;
if (balances.Balances != null && balances.Balances.ContainsKey(token.Address))
{
balance = balances.Balances[token.Address];
}
var price = prices[token.Address].Price;
var data = new GmxTokenData()
{
Address = token.Address,
Name = token.Name,
Symbol = token.Symbol,
Decimals = token.Decimals,
Price = price,
Balance = balance
};
result.Add(data);
}
return result;
}
private Dictionary<string, GmxTokenPriceData> GetTokenPrices(Web3 web3, List<GmxToken> tokens)
{
var result = new Dictionary<string, GmxTokenPriceData>();
foreach (var token in tokens)
{
// TODO fetch token price from oracle
result.Add(token.Address, new GmxTokenPriceData()
{
Price = new MarketPrice()
{
Min = 0,
Max = 0
},
Balance = 0,
TotalSupply = 0
});
}
return result;
}
public async Task<GmxTokenBalances> GetTokenBalances(Web3 web3, List<GmxToken> tokens, string? account = null)
{
var result = new GmxTokenBalances();
result.Balances = new Dictionary<string, BigInteger>();
var aggregateFunction = new AggregateFunction();
aggregateFunction.Calls = new List<Call>();
foreach (var token in tokens)
{
if (token.IsNative)
{
var nativeBalanceCallData = new GetEthBalanceFunction()
{
Addr = account ?? Nethereum.Util.AddressUtil.ZERO_ADDRESS
}.GetCallData();
aggregateFunction.Calls.Add(new Call
{
Target = Arbitrum.AddressV2.Multicall,
CallData = nativeBalanceCallData
});
}
else
{
var balanceCallData = new BalanceOfFunction()
{
Owner = account ?? GmxHelpers.GetRandomAddress()
}.GetCallData();
aggregateFunction.Calls.Add(new Call
{
Target = token.Address,
CallData = balanceCallData
});
}
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var returnCalls = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(aggregateFunction, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var balances = new BalanceOfOutputDTOBase().DecodeOutput(returnCalls.ReturnData[0].ToHex());
result.Balances.Add(token.Address, balances.Balance);
}
return result;
}
public async Task<List<FundingRate>> GetFundingRate(Web3 web3)
{
var market = await GetMarketInfo(web3);
return Map(market);
}
private List<FundingRate> Map(GmxMarketInfo market)
{
var longApr = GetFundingFactorPerPeriod(market, true, Periods[Enums.Timeframe.OneHour]) * 100;
var shortApr = GetFundingFactorPerPeriod(market, false, Periods[Enums.Timeframe.OneHour]) * 100;
var longFundingRate = new FundingRate()
{
Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
Rate = GmxHelpers.FormatAmount(longApr, 30, 4),
Direction = Enums.TradeDirection.Long
};
var shortFundingRate = new FundingRate()
{
Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
Rate = GmxHelpers.FormatAmount(shortApr, 30, 4),
Direction = Enums.TradeDirection.Short
};
return new List<FundingRate>() { longFundingRate, shortFundingRate };
}
public async Task<List<FundingRate>> GetFundingRates(Web3 web3)
{
var fundingRates = new List<FundingRate>();
var marketDatas = await GetMarketInfosAsync(web3);
foreach (var gmxMarketInfo in marketDatas)
{
var rates = Map(gmxMarketInfo);
fundingRates.AddRange(rates);
}
return fundingRates;
}
private BigInteger GetFundingFactorPerPeriod(GmxMarketInfo marketInfo, bool isLong, int periodInSeconds)
{
var fundingFactorPerSecond = marketInfo.Market.FundingFactorPerSecond;
var longsPayShorts = marketInfo.Market.LongsPayShorts;
var longInterestUsd = marketInfo.Infos.LongInterestUsd;
var shortInterestUsd = marketInfo.Infos.ShortInterestUsd;
var isLargerSide = isLong ? longsPayShorts : !longsPayShorts;
BigInteger factorPerSecond;
if (isLargerSide)
{
factorPerSecond = fundingFactorPerSecond * -1;
}
else
{
var largerInterestUsd = longsPayShorts ? longInterestUsd : shortInterestUsd;
var smallerInterestUsd = longsPayShorts ? shortInterestUsd : longInterestUsd;
var ratio = smallerInterestUsd > 0
? BigInteger.Multiply(largerInterestUsd, PRECISION) / smallerInterestUsd
: 0;
factorPerSecond = ApplyFactor(ratio, fundingFactorPerSecond);
}
return factorPerSecond * periodInSeconds;
}
private BigInteger ApplyFactor(BigInteger ratio, BigInteger fundingFactorPerSecond)
{
// Implement the logic for applying the factor based on the ratio
// This is a placeholder implementation
return BigInteger.Multiply(ratio, fundingFactorPerSecond) / PRECISION;
}
private static readonly BigInteger PRECISION = BigInteger.Pow(10, 18);
private Dictionary<Enums.Timeframe, int> Periods = new Dictionary<Enums.Timeframe, int>()
{
{ Enums.Timeframe.FiveMinutes, 300 },
{ Enums.Timeframe.FifteenMinutes, 900 },
{ Enums.Timeframe.OneHour, 3600 },
{ Enums.Timeframe.FourHour, 14400 },
{ Enums.Timeframe.OneDay, 86400 }
};
}

View File

@@ -0,0 +1,447 @@
using Managing.Common;
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
public static class TokenV2Service
{
public static List<GmxToken> TOKENS { get; set; } = new List<GmxToken>
{
new GmxToken
{
Name = "Ethereum",
Symbol = "ETH",
Decimals = 18,
Address = Nethereum.Util.AddressUtil.ZERO_ADDRESS,
IsNative = true,
IsShortable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880",
CoingeckoUrl = "https://www.coingecko.com/en/coins/ethereum",
IsV1Available = true
},
new GmxToken
{
Name = "Wrapped Ethereum",
Symbol = "WETH",
Decimals = 18,
Address = Constants.GMX.TokenAddress.WETH,
IsWrapped = true,
BaseSymbol = "ETH",
ImageUrl = "https://assets.coingecko.com/coins/images/2518/thumb/weth.png?1628852295",
CoingeckoUrl = "https://www.coingecko.com/en/coins/ethereum",
IsV1Available = true
},
new GmxToken
{
Name = "Wrapped Stacked Ethereum",
Symbol = "WSTETH",
Decimals = 18,
Address = Constants.GMX.TokenAddress.WSTETH,
IsWrapped = true,
BaseSymbol = "wstETH",
ImageUrl = "https://assets.coingecko.com/coins/images/2518/thumb/wsteth.png?1628852295",
CoingeckoUrl = "https://www.coingecko.com/en/coins/ethereum",
},
new GmxToken
{
Name = "Bitcoin (WBTC)",
Symbol = "BTC",
AssetSymbol = "WBTC",
Decimals = 8,
Address = Constants.GMX.TokenAddress.WBTC,
IsShortable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/26115/thumb/btcb.png?1655921693",
CoingeckoUrl = "https://www.coingecko.com/en/coins/wrapped-bitcoin",
ExplorerUrl = "https://arbiscan.io/address/0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f",
IsV1Available = true
},
new GmxToken
{
Name = "Arbitrum",
Symbol = "ARB",
Decimals = 18,
PriceDecimals = 3,
Address = Constants.GMX.TokenAddress.ARB,
ImageUrl =
"https://assets.coingecko.com/coins/images/16547/small/photo_2023-03-29_21.47.00.jpeg?1680097630",
CoingeckoUrl = "https://www.coingecko.com/en/coins/arbitrum",
ExplorerUrl = "https://arbiscan.io/token/0x912ce59144191c1204e64559fe8253a0e49e6548"
},
new GmxToken
{
Name = "Wrapped SOL (Wormhole)",
Symbol = "SOL",
AssetSymbol = "WSOL (Wormhole)",
Decimals = 9,
Address = Constants.GMX.TokenAddress.SOL,
ImageUrl = "https://assets.coingecko.com/coins/images/4128/small/solana.png?1640133422",
CoingeckoUrl = "https://www.coingecko.com/en/coins/solana",
ExplorerUrl = "https://arbiscan.io/token/0x2bCc6D6CdBbDC0a4071e48bb3B969b06B3330c07",
},
new GmxToken
{
Name = "Chainlink",
Symbol = "LINK",
Decimals = 18,
PriceDecimals = 3,
Address = Constants.GMX.TokenAddress.LINK,
IsStable = false,
IsShortable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/877/thumb/chainlink-new-logo.png?1547034700",
CoingeckoUrl = "https://www.coingecko.com/en/coins/chainlink",
ExplorerUrl = "https://arbiscan.io/token/0xf97f4df75117a78c1a5a0dbb814af92458539fb4",
IsV1Available = true
},
new GmxToken
{
Name = "Uniswap",
Symbol = "UNI",
Decimals = 18,
PriceDecimals = 3,
Address = Constants.GMX.TokenAddress.UNI,
IsStable = false,
IsShortable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/12504/thumb/uniswap-uni.png?1600306604",
CoingeckoUrl = "https://www.coingecko.com/en/coins/uniswap",
ExplorerUrl = "https://arbiscan.io/token/0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0",
IsV1Available = true
},
new GmxToken
{
Name = "Bridged USDC (USDC.e)",
Symbol = "USDC.e",
Decimals = 6,
Address = Constants.GMX.TokenAddress.USDCE,
IsStable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/6319/thumb/USD_Coin_icon.png?1547042389",
CoingeckoUrl = "https://www.coingecko.com/en/coins/bridged-usdc-arbitrum",
ExplorerUrl = "https://arbiscan.io/token/0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
IsV1Available = true
},
new GmxToken
{
Name = "USD Coin",
Symbol = "USDC",
Decimals = 6,
Address = Constants.GMX.TokenAddress.USDC,
IsStable = true,
IsV1Available = true,
ImageUrl = "https://assets.coingecko.com/coins/images/6319/thumb/USD_Coin_icon.png?1547042389",
CoingeckoUrl = "https://www.coingecko.com/en/coins/usd-coin",
ExplorerUrl = "https://arbiscan.io/address/0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
},
new GmxToken
{
Name = "Tether",
Symbol = "USDT",
Decimals = 6,
Address = Constants.GMX.TokenAddress.USDT,
IsStable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/325/thumb/Tether-logo.png?1598003707",
ExplorerUrl = "https://arbiscan.io/address/0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
CoingeckoUrl = "https://www.coingecko.com/en/coins/tether",
IsV1Available = true
},
new GmxToken
{
Name = "Dai",
Symbol = "DAI",
Decimals = 18,
Address = Constants.GMX.TokenAddress.DAI,
IsStable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734",
CoingeckoUrl = "https://www.coingecko.com/en/coins/dai",
ExplorerUrl = "https://arbiscan.io/token/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
IsV1Available = true
},
new GmxToken
{
Name = "USDe",
Symbol = "USDe",
Decimals = 18,
Address = Constants.GMX.TokenAddress.USDE,
IsStable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734",
CoingeckoUrl = "https://www.coingecko.com/en/coins/usde",
ExplorerUrl = "https://arbiscan.io/token/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
},
new GmxToken
{
Name = "Frax",
Symbol = "FRAX",
Decimals = 18,
Address = Constants.GMX.TokenAddress.FRAX,
IsStable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/13422/small/frax_logo.png?1608476506",
CoingeckoUrl = "https://www.coingecko.com/en/coins/frax",
ExplorerUrl = "https://arbiscan.io/token/0x17fc002b466eec40dae837fc4be5c67993ddbd6f",
IsV1Available = true
},
new GmxToken
{
Name = "Magic Internet Money",
Symbol = "MIM",
Decimals = 18,
Address = Constants.GMX.TokenAddress.MIM,
IsStable = true,
ImageUrl = "https://assets.coingecko.com/coins/images/16786/thumb/mimlogopng.png?1624979612",
CoingeckoUrl = "https://www.coingecko.com/en/coins/magic-internet-money",
ExplorerUrl = "https://arbiscan.io/token/0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A",
IsV1Available = true
},
new GmxToken
{
Name = "Bitcoin",
Symbol = "BTC",
Decimals = 8,
Address = Constants.GMX.TokenAddress.BTC,
IsSynthetic = true,
ImageUrl = "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579",
CoingeckoUrl = "https://www.coingecko.com/en/coins/bitcoin",
},
new GmxToken()
{
Name = "DogeCoin",
Symbol = "DOGE",
Decimals = 8,
PriceDecimals = 4,
Address = Constants.GMX.TokenAddress.DOGE,
IsSynthetic = true,
ImageUrl = "https://assets.coingecko.com/coins/images/5/small/dogecoin.png?1547792256",
CoingeckoUrl = "https://www.coingecko.com/en/coins/dogecoin",
},
new GmxToken
{
Name = "Litecoin",
Symbol = "LTC",
Decimals = 8,
Address = Constants.GMX.TokenAddress.LTC,
IsSynthetic = true,
ImageUrl = "https://assets.coingecko.com/coins/images/2/small/litecoin.png?1547033580",
CoingeckoUrl = "https://www.coingecko.com/en/coins/litecoin",
},
new GmxToken
{
Name = "XRP",
Symbol = "XRP",
Decimals = 6,
PriceDecimals = 4,
Address = Constants.GMX.TokenAddress.XRP,
ImageUrl = "https://assets.coingecko.com/coins/images/44/small/xrp-symbol-white-128.png?1605778731",
CoingeckoUrl = "https://www.coingecko.com/en/coins/xrp",
IsSynthetic = true,
},
new GmxToken
{
Name = "GMX",
Symbol = "GMX",
Address =
Constants.GMX.TokenAddress.GMX,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/18323/small/arbit.png?1631532468",
CoingeckoUrl = "https://www.coingecko.com/en/coins/gmx",
ExplorerUrl = "https://arbiscan.io/address/0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a",
IsPlatform = true
},
new GmxToken
{
Name = "Wrapped BNB (LayerZero)",
Symbol = "BNB",
AssetSymbol = "WBNB (LayerZero)",
Address = Constants.GMX.TokenAddress.WBNB,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/825/standard/bnb-icon2_2x.png?1696501970",
CoingeckoUrl = "https://www.coingecko.com/en/coins/bnb",
},
new GmxToken
{
Name = "Cosmos",
Symbol = "ATOM",
AssetSymbol = "ATOM",
Address = Constants.GMX.TokenAddress.ATOM,
Decimals = 6,
ImageUrl = "https://assets.coingecko.com/coins/images/1481/standard/cosmos_hub.png?1696502525",
CoingeckoUrl = "https://www.coingecko.com/en/coins/cosmos-hub",
IsSynthetic = true
},
new GmxToken
{
Name = "Near",
Symbol = "NEAR",
AssetSymbol = "NEAR",
Address = Constants.GMX.TokenAddress.NEAR,
Decimals = 24,
ImageUrl = "https://assets.coingecko.com/coins/images/10365/standard/near.jpg?1696510367",
CoingeckoUrl = "https://www.coingecko.com/en/coins/near",
IsSynthetic = true
},
new GmxToken
{
Name = "Aave",
Symbol = "AAVE",
AssetSymbol = "AAVE",
Address = Constants.GMX.TokenAddress.AAVE,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/12645/standard/AAVE.png?1696512452",
CoingeckoUrl = "https://www.coingecko.com/en/coins/aave"
},
new GmxToken
{
Name = "Wrapped AVAX (Wormhole)",
Symbol = "AVAX",
AssetSymbol = "WAVAX (Wormhole)",
Address = Constants.GMX.TokenAddress.AVAX,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/12559/small/coin-round-red.png?1604021818",
CoingeckoUrl = "https://www.coingecko.com/en/coins/avalanche"
},
new GmxToken
{
Name = "Optimism",
Symbol = "OP",
Address = Constants.GMX.TokenAddress.OP,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/25244/standard/Optimism.png?1696524385",
CoingeckoUrl = "https://www.coingecko.com/en/coins/optimism"
},
new GmxToken
{
Name = "Pepe",
Symbol = "PEPE",
Address = Constants.GMX.TokenAddress.PEPE,
Decimals = 18,
PriceDecimals = 8,
ImageUrl = "https://assets.coingecko.com/coins/images/29850/standard/pepe-token.jpeg?1696528776",
CoingeckoUrl = "https://www.coingecko.com/en/coins/pepe"
},
new GmxToken
{
Name = "dogwifhat",
Symbol = "WIF",
Address = Constants.GMX.TokenAddress.WIF,
Decimals = 6,
ImageUrl = "https://assets.coingecko.com/coins/images/33566/standard/dogwifhat.jpg?1702499428",
CoingeckoUrl = "https://www.coingecko.com/en/coins/dogwifhat"
},
new GmxToken
{
Name = "Shiba Inu",
Symbol = "SHIB",
Address = Constants.GMX.TokenAddress.SHIB,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/11939/standard/shiba.png?1696511800",
CoingeckoUrl = "https://www.coingecko.com/en/coins/shiba-inu",
IsSynthetic = true,
PriceDecimals = 8
},
new GmxToken
{
Name = "APE Coin",
Symbol = "APE",
Address = Constants.GMX.TokenAddress.APE,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/11939/standard/shiba.png?1696511800",
CoingeckoUrl = "https://www.coingecko.com/en/coins/shiba-inu",
IsSynthetic = true,
PriceDecimals = 8
},
new GmxToken
{
Name = "Stacks",
Symbol = "STX",
Address = Constants.GMX.TokenAddress.STX,
Decimals = 6,
ImageUrl = "https://assets.coingecko.com/coins/images/2069/standard/Stacks_Logo_png.png?1709979332",
CoingeckoUrl = "https://www.coingecko.com/en/coins/stacks",
IsSynthetic = true,
},
new GmxToken
{
Name = "ORDI",
Symbol = "ORDI",
Address = Constants.GMX.TokenAddress.ORDI,
Decimals = 18,
ImageUrl = "https://assets.coingecko.com/coins/images/30162/standard/ordi.png?1696529082",
CoingeckoUrl = "https://www.coingecko.com/en/coins/ordi",
IsSynthetic = true,
},
};
public static GmxToken NATIVE_TOKEN = TOKENS.First(t => t.IsNative);
public static GmxToken WRAPPRED_NATIVE_TOKEN = TOKENS.First(t => t.IsWrapped);
public static List<GmxToken> GetTokens()
{
return TOKENS;
}
public static GmxToken GetTokenByAddress(string address)
{
var token = TOKENS.FirstOrDefault(t => GmxV2Helpers.SameAddress(t.Address, address));
if (token == null)
throw new Exception($"Token with address '{address}' not found");
return token;
}
public static string ConvertTokenAddress(string address, string convertTo = null)
{
if (convertTo == "wrapped" && address == NATIVE_TOKEN.Address)
{
return WRAPPRED_NATIVE_TOKEN.Address;
}
if (convertTo == "native" && address == WRAPPRED_NATIVE_TOKEN.Address)
{
return NATIVE_TOKEN.Address;
}
return address;
}
public static string GetMarketFullName(string indexToken, string longToken, string shortToken, bool isSpotOnly)
{
return $"{GetMarketIndexName(indexToken, isSpotOnly)} [{GetMarketPoolName(longToken, shortToken)}]";
}
public static string GetMarketIndexName(string indexToken, bool isSpotOnly)
{
if (isSpotOnly)
{
return "SPOT-ONLY";
}
return indexToken;
}
public static string GetMarketPoolName(string longToken, string shortToken)
{
if (longToken == shortToken)
{
return longToken;
}
return $"{longToken}-{shortToken}";
}
}
public class GmxToken
{
public string Name { get; set; }
public string Symbol { get; set; }
public int Decimals { get; set; }
public string Address { get; set; }
public bool IsNative { get; set; }
public bool IsShortable { get; set; }
public string ImageUrl { get; set; }
public string CoingeckoUrl { get; set; }
public bool IsV1Available { get; set; }
public bool IsStable { get; set; }
public string ExplorerUrl { get; set; }
public string BaseSymbol { get; set; }
public string AssetSymbol { get; set; }
public bool IsWrapped { get; set; }
public int PriceDecimals { get; set; }
public bool IsSynthetic { get; set; }
public bool IsPlatform { get; set; }
}