Files
managing-apps/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Service.cs
Oda 65bdb8e34f GMX v2 - Trading (#7)
* Move PrivateKeys.cs

* Update gitignore

* Update gitignore

* updt

* Extract GmxServiceTests.cs

* Refact

* update todo

* Update code

* Fix hashdata

* Replace static token hashed datas

* Set allowance

* Add get orders

* Add get orders tests

* Add ignore

* add close orders

* revert

* Add get gas limit

* Start increasePosition. Todo: Finish GetExecutionFee and estimateGas

* little refact

* Update gitignore

* Fix namespaces and clean repo

* Add tests samples

* Add execution fee

* Add increase position

* Handle backtest on the frontend

* Add tests

* Update increase

* Test increase

* fix increase

* Fix size

* Start get position

* Update get positions

* Fix get position

* Update rpc and trade mappers

* Finish close position

* Fix leverage
2025-01-30 23:06:22 +07:00

1375 lines
55 KiB
C#

using System.Numerics;
using Managing.ABI.GmxV2.DataStore;
using Managing.ABI.GmxV2.DataStore.ContractDefinition;
using Managing.ABI.GmxV2.ExchangeRouter;
using Managing.ABI.GmxV2.ExchangeRouter.ContractDefinition;
using Managing.ABI.GmxV2.Multicall3.ContractDefinition;
using Managing.ABI.GmxV2.Reader;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Common;
using Managing.Core;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Contracts;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Hex.HexTypes;
using Nethereum.Util;
using Nethereum.Web3;
using GetAccountOrdersFunction = Managing.ABI.GmxV2.Reader.ContractDefinition.GetAccountOrdersFunction;
using GetAccountOrdersOutputDTO = Managing.ABI.GmxV2.Reader.ContractDefinition.GetAccountOrdersOutputDTO;
using GetAccountPositionInfoListFunction =
Managing.ABI.GmxV2.SyntheticsReader.ContractDefinition.GetAccountPositionInfoListFunction;
using GetAccountPositionInfoListOutputDTO =
Managing.ABI.GmxV2.SyntheticsReader.ContractDefinition.GetAccountPositionInfoListOutputDTO;
using GetMarketInfoFunction = Managing.ABI.GmxV2.Reader.ContractDefinition.GetMarketInfoFunction;
using GetMarketInfoOutputDTO = Managing.ABI.GmxV2.Reader.ContractDefinition.GetMarketInfoOutputDTO;
using GetMarketsFunction = Managing.ABI.GmxV2.Reader.ContractDefinition.GetMarketsFunction;
using GetMarketsOutputDTO = Managing.ABI.GmxV2.Reader.ContractDefinition.GetMarketsOutputDTO;
using GetMarketTokenPriceFunction = Managing.ABI.GmxV2.Reader.ContractDefinition.GetMarketTokenPriceFunction;
using GetMarketTokenPriceOutputDTO = Managing.ABI.GmxV2.Reader.ContractDefinition.GetMarketTokenPriceOutputDTO;
using MarketPrices = Managing.ABI.GmxV2.Reader.ContractDefinition.MarketPrices;
namespace Managing.Infrastructure.Evm.Services.Gmx;
public class GmxV2Service
{
private readonly GmxV2Encoder _encoder = new GmxV2Encoder();
private readonly BigInteger _defaultCount = new BigInteger(1000);
private readonly OracleKeeperService _oracleKeeperService = new OracleKeeperService();
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 = 10
}.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 longInterestUsingLongTokenCallData = new GetUintFunction()
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.LongToken),
("bool", true)
])
}.GetCallData();
var longInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingLongTokenCallData
};
var longInterestUsingShortTokenCallData = new GetUintFunction()
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.ShortToken),
("bool", true)
])
}.GetCallData();
var longInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingShortTokenCallData
};
var shortInterestUsingLongTokenCallData = new GetUintFunction()
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.LongToken),
("bool", false)
])
}.GetCallData();
var shortInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingLongTokenCallData
};
var shortInterestUsingShortTokenCallData = new GetUintFunction()
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.ShortToken),
("bool", false)
])
}.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,
_encoder.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 = _encoder.HashString(Constants.GMX.MARKET_DISABLED_KEY);
var parameters = new List<(string dataType, object dataValues)>
{
("bytes32", hashString),
("address", market.MarketToken)
};
var response = await dataStoreService.GetBoolQueryAsync(_encoder.HashData(parameters));
return response;
}
public async Task<BigInteger> GetLongInterestAmount(Web3 web3)
{
var dataStoreService = new DataStoreService(web3, Arbitrum.AddressV2.DataStore);
var market = (await GetMarketsAsync(web3)).First();
var parameters = new List<(string dataType, object dataValues)>
{
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.LongToken),
("bool", true)
};
var response = await dataStoreService.GetUintQueryAsync(_encoder.HashData(parameters));
return response;
}
public async Task<List<GmxMarketInfo>> GetMarketInfosAsync(Web3 web3)
{
var markets = await GetMarketsAsync(web3);
var readerResult = new List<GmxMarketInfo>();
var readerCalls = new List<Call>();
var datastoreCalls = new List<Call>();
markets = markets.Where(m => !m.IsSpotOnly).ToList();
var tokensData = await GetTokensData(web3);
foreach (var market in markets)
{
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 = _encoder.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 = _encoder.HashString(Constants.GMX.MAX_PNL_FACTOR_FOR_TRADERS),
Maximize = false
}.GetCallData();
var getMarketTokenPriceMinCall = new Call
{
Target = Arbitrum.AddressV2.Reader,
CallData = getMarketTokenPriceMinCallData
};
readerCalls.Add(getMarketInfoCall);
readerCalls.Add(getMarketTokenPriceCall);
readerCalls.Add(getMarketTokenPriceMinCall);
var longInterestUsingLongTokenCallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.LongToken),
("bool", true)
])
}.GetCallData();
var longInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingLongTokenCallData
};
var longInterestUsingShortTokenCallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.ShortToken),
("bool", true)
])
}.GetCallData();
var longInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = longInterestUsingShortTokenCallData
};
var shortInterestUsingLongTokenCallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.LongToken),
("bool", false)
])
}.GetCallData();
var shortInterestUsingLongTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingLongTokenCallData
};
var shortInterestUsingShortTokenCallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.OPEN_INTEREST)),
("address", market.MarketToken),
("address", market.ShortToken),
("bool", false)
])
}.GetCallData();
var shortInterestUsingShortTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = shortInterestUsingShortTokenCallData
};
datastoreCalls.Add(longInterestUsingLongTokenCall);
datastoreCalls.Add(longInterestUsingShortTokenCall);
datastoreCalls.Add(shortInterestUsingLongTokenCall);
datastoreCalls.Add(shortInterestUsingShortTokenCall);
}
var readerMulticall = new AggregateFunction { Calls = readerCalls };
var datastoreMulticall = new AggregateFunction { Calls = datastoreCalls };
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var readerCallResults = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(readerMulticall, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var datastoreCallResults = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(datastoreMulticall, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
int readerCallIndex = 0;
int datastoreCallIndex = 0;
foreach (var market in markets)
{
var marketInfo =
new GetMarketInfoOutputDTO().DecodeOutput(readerCallResults.ReturnData[readerCallIndex++].ToHex());
var marketTokenPriceMax =
new GetMarketTokenPriceOutputDTO().DecodeOutput(readerCallResults.ReturnData[readerCallIndex++]
.ToHex());
var marketTokenPriceMin =
new GetMarketTokenPriceOutputDTO().DecodeOutput(readerCallResults.ReturnData[readerCallIndex++]
.ToHex());
var longInterestUsingLongToken = new GetUintOutputDTO()
.DecodeOutput(datastoreCallResults.ReturnData[datastoreCallIndex++].ToHex()).ReturnValue1;
var longInterestUsingShortToken = new GetUintOutputDTO()
.DecodeOutput(datastoreCallResults.ReturnData[datastoreCallIndex++].ToHex()).ReturnValue1;
var shortInterestUsingLongToken = new GetUintOutputDTO()
.DecodeOutput(datastoreCallResults.ReturnData[datastoreCallIndex++].ToHex()).ReturnValue1;
var shortInterestUsingShortToken = new GetUintOutputDTO()
.DecodeOutput(datastoreCallResults.ReturnData[datastoreCallIndex++].ToHex()).ReturnValue1;
var marketInfos = GmxMappers.Map(marketInfo.ReturnValue1);
readerResult.Add(new GmxMarketInfo
{
Market = marketInfos,
Infos = new GmxMarketInfos
{
LongInterestUsd = longInterestUsingLongToken + longInterestUsingShortToken,
ShortInterestUsd = shortInterestUsingLongToken + shortInterestUsingShortToken
},
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, bool fastCall = false)
{
var tokens = TokenV2Service.GetTokens().ToList();
var balances = fastCall ? null : await GetTokenBalances(web3, tokens);
var prices = await GetTokenPrices(web3, tokens);
var result = new List<GmxTokenData>();
foreach (var token in tokens)
{
try
{
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);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
return result;
}
private async Task<Dictionary<string, GmxTokenPriceData>> GetTokenPrices(Web3 web3, List<GmxToken> tokens)
{
var result = new Dictionary<string, GmxTokenPriceData>();
var tickersPrices = await _oracleKeeperService.GetTickers();
foreach (var token in tokens)
{
var price = tickersPrices.FirstOrDefault(t => GmxV2Helpers.SameAddress(t.TokenAddress, token.Address));
if (price == null && GmxV2Helpers.SameAddress(token.Address, Arbitrum.Address.Zero))
{
price = tickersPrices.FirstOrDefault(t =>
GmxV2Helpers.SameAddress(t.TokenAddress, Constants.GMX.TokenAddress.WETH));
}
result.Add(token.Address, new GmxTokenPriceData()
{
Price = new MarketPrice()
{
Min = price != null ? BigInteger.Parse(price.MinPrice) : BigInteger.Zero,
Max = price != null ? BigInteger.Parse(price.MaxPrice) : BigInteger.Zero,
},
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>();
var index = 0;
foreach (var token in tokens)
{
if (token.IsNative)
{
var nativeBalanceCallData = new GetEthBalanceFunction()
{
Addr = account ?? 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);
foreach (var callResult in returnCalls.ReturnData)
{
var balance = new BalanceOfOutputDTOBase().DecodeOutput(callResult.ToHex());
result.Balances.Add(tokens[index].Address, balance.Balance);
index++;
}
return result;
}
public async Task<List<FundingRate>> GetFundingRate(Web3 web3)
{
var market = await GetMarketInfo(web3);
return GmxMappers.Map(market);
}
public async Task<List<FundingRate>> GetFundingRates(Web3 web3)
{
var fundingRates = new List<FundingRate>();
var marketDatas = await GetMarketInfosAsync(web3);
foreach (var gmxMarketInfo in marketDatas)
{
if (!Constants.GMX.Config.DeltaNeutralTickers.Contains(GmxHelpers.GetTicker(gmxMarketInfo.Market.Symbol)))
continue;
var rates = GmxMappers.Map(gmxMarketInfo);
fundingRates.AddRange(rates);
}
return fundingRates;
}
public async Task<List<GmxV2Order>> GetOrders(Web3 web3, string publicAddress, Enums.Ticker ticker)
{
var marketAddress = GmxV2Helpers.GetMarketAddress(ticker);
var orders = await GetAllOrders(web3, publicAddress);
return orders.Where(o => o.MarketAddress == marketAddress).ToList();
}
private async Task<List<GmxV2Order>> GetAllOrders(Web3 web3, string publicAddress)
{
// Call Datastore and reader to construct open orders list
var aggregateFunction = new AggregateFunction();
aggregateFunction.Calls = new List<Call>();
var countCallData = new GetBytes32CountFunction
{
SetKey = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.ACCOUNT_ORDER_LIST_KEY)),
("address", publicAddress),
])
}.GetCallData();
var countCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = countCallData
};
var keysCallData = new GetBytes32ValuesAtFunction
{
SetKey = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.ACCOUNT_ORDER_LIST_KEY)),
("address", publicAddress),
]),
Start = 0,
End = _defaultCount
}.GetCallData();
var keysCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = keysCallData
};
aggregateFunction.Calls.Add(countCall);
aggregateFunction.Calls.Add(keysCall);
var orderCallData = new GetAccountOrdersFunction
{
DataStore = Arbitrum.AddressV2.DataStore,
Account = publicAddress,
Start = 0,
End = _defaultCount
}.GetCallData();
var orderCall = new Call
{
Target = Arbitrum.AddressV2.Reader,
CallData = orderCallData
};
aggregateFunction.Calls.Add(orderCall);
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var returnCalls = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(aggregateFunction, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var count = new GetBytes32CountOutputDTO().DecodeOutput(returnCalls.ReturnData[0].ToHex()).ReturnValue1;
var keys = new GetBytes32ValuesAtOutputDTO().DecodeOutput(returnCalls.ReturnData[1].ToHex()).ReturnValue1;
var orders = new GetAccountOrdersOutputDTO().DecodeOutput(returnCalls.ReturnData[2].ToHex()).ReturnValue1;
var result = new List<GmxV2Order>();
for (int i = 0; i < count; i++)
{
var key = keys[i];
var order = orders[i];
var date = DateHelpers.GetFromUnixTimestamp((int)order.Numbers.UpdatedAtBlock);
var orderData = new GmxV2Order()
{
Key = key,
Account = order.Addresses.Account,
Receiver = order.Addresses.Receiver,
CallbackContract = order.Addresses.CallbackContract,
MarketAddress = order.Addresses.Market,
InitialCollateralTokenAddress = order.Addresses.InitialCollateralToken,
SwapPath = order.Addresses.SwapPath,
SizeDeltaUsd = order.Numbers.SizeDeltaUsd,
InitialCollateralDeltaAmount = order.Numbers.InitialCollateralDeltaAmount,
ContractTriggerPrice = order.Numbers.TriggerPrice,
ContractAcceptablePrice = order.Numbers.AcceptablePrice,
ExecutionFee = order.Numbers.ExecutionFee,
CallbackGasLimit = order.Numbers.CallbackGasLimit,
MinOutputAmount = order.Numbers.MinOutputAmount,
UpdatedAtBlock = order.Numbers.UpdatedAtBlock,
IsLong = order.Flags.IsLong,
ShouldUnwrapNativeToken = order.Flags.ShouldUnwrapNativeToken,
IsFrozen = order.Flags.IsFrozen,
OrderType = MiscExtensions.ParseEnum<GmxV2OrderType>(order.Numbers.OrderType.ToString()),
DecreasePositionSwapType =
MiscExtensions.ParseEnum<GmxV2DecreasePositionSwapType>(order.Numbers.DecreasePositionSwapType
.ToString()),
Data = order,
Date = date
};
result.Add(orderData);
}
return result;
}
public async Task<bool> CancelOrders(Web3 web3, string accountKey, Enums.Ticker ticker)
{
var orders = await new GmxV2Service().GetOrders(web3, accountKey, ticker);
if (orders.Count == 0)
{
return true;
}
// Somehow, it's only possible to cancel one order at a time
var results = new List<bool>();
foreach (var order in orders)
{
var multicallFunction = new MulticallFunction
{
Data = []
};
var cancelOrderCallData = new CancelOrderFunction
{
Key = order.Key
};
multicallFunction.Data.Add(cancelOrderCallData.GetCallData());
var queryHandler = web3.Eth.GetContractTransactionHandler<MulticallFunction>();
try
{
var returnCalls =
await queryHandler.SendRequestAndWaitForReceiptAsync(Arbitrum.AddressV2.ExchangeRouter,
multicallFunction);
results.Add(returnCalls.Status.Value == 1);
}
catch (Exception e)
{
results.Add(false);
}
}
return results.All(r => r) && results.Count == orders.Count;
}
public async Task<Trade> IncreasePosition(Web3 web3, string accountKey, Enums.Ticker ticker,
Enums.TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
{
var defaultLeverage = leverage ?? 1;
var marketAddress = GmxV2Helpers.GetMarketAddress(ticker);
var fromToken = TokenV2Service.GetByTicker(ticker);
var multiCallFunction = new MulticallFunction();
multiCallFunction.Data = new List<byte[]>();
var executionFee = await GetExecutionFee(web3, GmxV2Enums.TradeType.Increase);
var usdcToken = TokenV2Service.TOKENS.First(t => t.Symbol == Enums.Ticker.USDC.ToString());
var collateral = price * quantity;
var initialCollateralAmount = GmxV2Helpers.ParseValue(collateral, usdcToken.Decimals);
var triggerPrice = GmxV2Helpers.ConvertToContractPrice(price, fromToken.Decimals, true);
var sizeDeltaUsd = GmxV2Helpers.ParseValue(collateral * defaultLeverage, 30);
var acceptablePrice = GmxV2Helpers.GetAcceptablePrice(price, direction == Enums.TradeDirection.Long);
var totalWntAmount = executionFee.FeeAmount;
var sendWntFunction = new SendWntFunction
{
Receiver = Arbitrum.AddressV2.OrderVault,
Amount = totalWntAmount
};
multiCallFunction.Data.Add(sendWntFunction.GetCallData());
var sendTokenCallData = new SendTokensFunction
{
Token = usdcToken.Address,
Receiver = Arbitrum.AddressV2.OrderVault,
Amount = initialCollateralAmount
};
multiCallFunction.Data.Add(sendTokenCallData.GetCallData());
var createOrderCallData = new CreateOrderFunction
{
Params = new CreateOrderParams
{
Addresses = new CreateOrderParamsAddresses
{
CallbackContract = Arbitrum.Address.Zero,
CancellationReceiver = Arbitrum.Address.Zero,
InitialCollateralToken = usdcToken.Address,
Market = marketAddress,
Receiver = accountKey,
SwapPath = new List<string>(),
UiFeeReceiver = Arbitrum.Address.Zero
},
AutoCancel = false,
IsLong = direction == Enums.TradeDirection.Long,
DecreasePositionSwapType = 0,
Numbers = new CreateOrderParamsNumbers
{
AcceptablePrice = acceptablePrice,
CallbackGasLimit = 0,
ExecutionFee = executionFee.FeeAmount,
InitialCollateralDeltaAmount = initialCollateralAmount, // Should be 0 somehow
SizeDeltaUsd = sizeDeltaUsd,
TriggerPrice = triggerPrice,
MinOutputAmount = 0,
},
OrderType = 3,
ReferralCode = new byte[32],
ShouldUnwrapNativeToken = false
}
};
multiCallFunction.Data.Add(createOrderCallData.GetCallData());
multiCallFunction.Gas = new HexBigInteger(8_000_000);
var gasPriceData = await GetCurrentGasPrice(web3);
multiCallFunction.GasPrice = new HexBigInteger(gasPriceData);
multiCallFunction.AmountToSend = totalWntAmount;
try
{
var exchangeRouterService = new ExchangeRouterService(web3, Arbitrum.AddressV2.ExchangeRouter);
var receipt = await exchangeRouterService.MulticallRequestAndWaitForReceiptAsync(multiCallFunction);
var trade = new Trade(DateTime.UtcNow,
direction,
Enums.TradeStatus.Requested,
Enums.TradeType.Limit,
ticker,
quantity,
price,
leverage,
receipt.TransactionHash,
"");
return trade;
}
catch (Exception e)
{
Console.WriteLine($"Error increasing position: {e.Message}");
throw;
}
}
public async Task<Trade> DecreasePosition(
Web3 web3,
string accountAddress,
Enums.Ticker ticker,
Enums.TradeDirection direction,
decimal closePrice,
decimal quantity,
decimal leverage)
{
var trade = new Trade(DateTime.UtcNow,
direction,
Enums.TradeStatus.Cancelled,
Enums.TradeType.StopMarket,
ticker,
quantity,
closePrice,
leverage,
"",
"");
var marketAddress = GmxV2Helpers.GetMarketAddress(ticker);
var currentPosition =
(await GetGmxPositionsV2(web3, accountAddress)).FirstOrDefault(p =>
GmxV2Helpers.SameAddress(marketAddress, p.MarketAddress));
if (currentPosition == null || currentPosition.SizeInUsd == 0) return trade;
// var collateralToken = TokenV2Service.TOKENS.First(t => t.Symbol == "USDC");
var executionFee = await GetExecutionFee(web3, GmxV2Enums.TradeType.Decrease);
var acceptablePrice = GmxV2Helpers.GetAcceptablePrice(closePrice, direction == Enums.TradeDirection.Long);
// var collateral = closePrice * quantity;
// var initialCollateralAmount = GmxV2Helpers.ParseValue(collateral, collateralToken.Decimals);
var quantityLeveraged = quantity * leverage;
// var sizeDeltaUsd = GmxV2Helpers.ParseValue(quantityLeveraged * closePrice, 0);
var sendWntFunction = new SendWntFunction
{
Receiver = Arbitrum.AddressV2.OrderVault,
Amount = executionFee.FeeAmount
}.GetCallData();
var createOrderFunction = new CreateOrderFunction
{
Params = new CreateOrderParams
{
Addresses = new CreateOrderParamsAddresses
{
Market = marketAddress,
InitialCollateralToken = currentPosition.CollateralTokenAddress,
CancellationReceiver = Arbitrum.Address.Zero,
Receiver = accountAddress,
CallbackContract = Arbitrum.Address.Zero,
SwapPath = new List<string>(),
UiFeeReceiver = Arbitrum.Address.Zero
},
Numbers = new CreateOrderParamsNumbers
{
SizeDeltaUsd = currentPosition.SizeInUsd,
InitialCollateralDeltaAmount = currentPosition.CollateralAmount, // No collateral withdrawal
TriggerPrice = 0,
AcceptablePrice = acceptablePrice,
ExecutionFee = executionFee.FeeAmount,
MinOutputAmount = 0,
ValidFromTime = 0,
CallbackGasLimit = 0
},
OrderType = 4,
DecreasePositionSwapType = 1,
IsLong = currentPosition.IsLong,
ShouldUnwrapNativeToken = false,
ReferralCode = new byte[32]
}
};
// 5. Configure transaction
var gasPriceData = await GetCurrentGasPrice(web3);
var multicallFunction = new MulticallFunction
{
Data = new List<byte[]> { sendWntFunction, createOrderFunction.GetCallData() },
AmountToSend = executionFee.FeeAmount,
Gas = new HexBigInteger(7_000_000),
GasPrice = new HexBigInteger(gasPriceData)
};
try
{
// 7. Execute transaction
var exchangeRouterService = new ExchangeRouterService(web3, Arbitrum.AddressV2.ExchangeRouter);
var receipt = await exchangeRouterService.MulticallRequestAndWaitForReceiptAsync(
multicallFunction
);
return new Trade(
DateTime.UtcNow,
direction,
Enums.TradeStatus.Requested,
Enums.TradeType.Market,
ticker,
quantity,
closePrice,
leverage,
receipt.TransactionHash,
""
);
}
catch (Exception ex)
{
Console.WriteLine($"Decrease position failed: {ex.Message}");
trade.SetMessage(ex.Message);
return trade;
}
}
// V2-specific helper methods
private (BigInteger triggerPrice, BigInteger acceptablePrice) GetDecreaseOrderPrices(
decimal price,
bool isLong,
int priceDecimals,
decimal slippageBps)
{
var slippageMultiplier = isLong
? 1m - (slippageBps / 10000m)
: 1m + (slippageBps / 10000m);
return (
GmxV2Helpers.ConvertToContractPrice(price, priceDecimals),
GmxV2Helpers.ConvertToContractPrice(price * slippageMultiplier, 30)
);
}
private async Task<HexBigInteger> GetCurrentGasPrice(Web3 web3)
{
var gasPrice = await web3.Eth.GasPrice.SendRequestAsync();
return new HexBigInteger(gasPrice.Value * 115 / 100); // Add 15% buffer
}
public async Task<GmxV2GasLimit> GetGasLimit(Web3 web3)
{
var aggregateFunction = new AggregateFunction();
aggregateFunction.Calls = new List<Call>();
var depositSingleTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.DEPOSIT_GAS_LIMIT_KEY)),
("bool", true)
])
}.GetCallData()
};
var depositMultiTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.DEPOSIT_GAS_LIMIT_KEY)),
("bool", false)
])
}.GetCallData()
};
var withdrawalMultiTokenCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashData([
("bytes32", _encoder.HashString(Constants.GMX.WITHDRAWAL_GAS_LIMIT_KEY)),
])
}.GetCallData()
};
var shiftCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.SHIFT_GAS_LIMIT_KEY)
}.GetCallData()
};
var singleSwapCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.SINGLE_SWAP_GAS_LIMIT_KEY)
}.GetCallData()
};
var swapOrderCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.SWAP_ORDER_GAS_LIMIT_KEY)
}.GetCallData()
};
var increaseOrderCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.INCREASE_ORDER_GAS_LIMIT_KEY)
}.GetCallData()
};
var decreaseOrderCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.DECREASE_ORDER_GAS_LIMIT_KEY)
}.GetCallData()
};
var estimatedGasFeeBaseAmountCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.ESTIMATED_GAS_FEE_BASE_AMOUNT)
}.GetCallData()
};
var estimatedGasFeePerOraclePriceCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.ESTIMATED_GAS_FEE_PER_ORACLE_PRICE)
}.GetCallData()
};
var estimatedFeeMultiplierFactorCall = new Call
{
Target = Arbitrum.AddressV2.DataStore,
CallData = new GetUintFunction
{
Key = _encoder.HashString(Constants.GMX.ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR)
}.GetCallData()
};
aggregateFunction.Calls.Add(depositSingleTokenCall);
aggregateFunction.Calls.Add(depositMultiTokenCall);
aggregateFunction.Calls.Add(withdrawalMultiTokenCall);
aggregateFunction.Calls.Add(shiftCall);
aggregateFunction.Calls.Add(singleSwapCall);
aggregateFunction.Calls.Add(swapOrderCall);
aggregateFunction.Calls.Add(increaseOrderCall);
aggregateFunction.Calls.Add(decreaseOrderCall);
aggregateFunction.Calls.Add(estimatedGasFeeBaseAmountCall);
aggregateFunction.Calls.Add(estimatedGasFeePerOraclePriceCall);
aggregateFunction.Calls.Add(estimatedFeeMultiplierFactorCall);
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var returnCalls = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(aggregateFunction, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var gasLimit = new GmxV2GasLimit();
gasLimit.DepositSingleToken =
new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[0].ToHex()).ReturnValue1;
gasLimit.DepositMultiToken =
new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[1].ToHex()).ReturnValue1;
gasLimit.WithdrawalMultiToken =
new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[2].ToHex()).ReturnValue1;
gasLimit.Shift = new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[3].ToHex()).ReturnValue1;
gasLimit.SingleSwap = new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[4].ToHex()).ReturnValue1;
gasLimit.SwapOrder = new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[5].ToHex()).ReturnValue1;
gasLimit.IncreaseOrder = new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[6].ToHex()).ReturnValue1;
gasLimit.DecreaseOrder = new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[7].ToHex()).ReturnValue1;
gasLimit.EstimatedGasFeeBaseAmount =
new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[8].ToHex()).ReturnValue1;
gasLimit.EstimatedGasFeePerOraclePrice =
new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[9].ToHex()).ReturnValue1;
gasLimit.EstimatedFeeMultiplierFactor =
new GetUintOutputDTO().DecodeOutput(returnCalls.ReturnData[10].ToHex()).ReturnValue1;
return gasLimit;
}
public async Task<GmxV2ExecutionFee> GetExecutionFee(
Web3 web3,
GmxV2Enums.TradeType tradeType,
int swapPathLength = 0)
{
const int PRECISION = 30;
// 1. Get gas configuration from chain
var gasLimits = await GetGasLimits(web3);
var gasPrice = await web3.Eth.GasPrice.SendRequestAsync();
var tokensData = await GetTokensData(web3);
// 2. Calculate oracle price count (matches SDK logic)
var oraclePriceCount = (swapPathLength > 0 ? 2 : 1) * (swapPathLength + 1);
// 3. Get base gas limit with oracle cost
var operationGasLimit = tradeType switch
{
GmxV2Enums.TradeType.Increase => 6_500_000,
GmxV2Enums.TradeType.Decrease => 5_500_000,
_ => throw new ArgumentException("Invalid trade type")
};
var feeAmount = operationGasLimit * gasPrice.Value;
// 5. Apply multiplier with precise integer math
var applyFactor = (operationGasLimit * gasLimits.EstimatedFeeMultiplierFactor) /
BigInteger.Pow(10, PRECISION);
// 7. Get native token data
var nativeToken = tokensData.First(t =>
t.Symbol.Equals("ETH", StringComparison.OrdinalIgnoreCase) ||
t.Symbol.Equals("WETH", StringComparison.OrdinalIgnoreCase));
return new GmxV2ExecutionFee
{
FeeAmount = feeAmount,
// FeeUsd = ConvertToUsd(feeAmount, nativeToken.Decimals, nativeToken.Price.Min),
NativeToken = nativeToken
};
}
// Helper methods
private BigInteger ConvertToUsd(BigInteger feeAmount, int decimals, decimal tokenPrice)
{
var tokenValue = (decimal)feeAmount / (decimal)BigInteger.Pow(10, decimals);
return (BigInteger)(tokenValue * tokenPrice);
}
private async Task<GmxV2GasLimit> GetGasLimits(Web3 web3)
{
// Implement chain-specific gas limit configuration
return new GmxV2GasLimit
{
EstimatedGasFeeBaseAmount = 200_000,
EstimatedGasFeePerOraclePrice = 50_000,
EstimatedFeeMultiplierFactor = BigInteger.Parse("1000000000000000000") // 1e18
};
}
// Helper methods
private BigInteger EstimateOrderOraclePriceCount(int swapPathLength)
{
// Matches SDK's estimateOrderOraclePriceCount
return (swapPathLength > 0 ? 2 : 1) * (swapPathLength + 1);
}
private async Task<BigInteger> GetGasPriceInWei(Web3 web3)
{
var gasPrice = await web3.Eth.GasPrice.SendRequestAsync();
return gasPrice.Value; // Already in wei
}
private BigInteger GetOperationGasLimit(GmxV2Enums.TradeType tradeType)
{
// Implement based on GMX's internal logic:
// - Increase: 6_500_000
// - Decrease: 7_000_000
// - Swap: 8_000_000
return tradeType switch
{
GmxV2Enums.TradeType.Increase => 6_500_000,
GmxV2Enums.TradeType.Decrease => 7_000_000,
GmxV2Enums.TradeType.Swap => 8_000_000,
_ => throw new ArgumentException("Invalid trade type")
};
}
public BigInteger GetEstimatedGasLimit(GmxV2GasLimit gasLimit, int swapCount,
GmxV2Enums.TradeType tradeType)
{
switch (tradeType)
{
case GmxV2Enums.TradeType.Swap:
break;
case GmxV2Enums.TradeType.Increase:
// To handle collateral swap must implement get swap path
return EstimateExecuteIncreaseOrderGasLimit(gasLimit, swapCount);
case GmxV2Enums.TradeType.Decrease:
return EstimateExecuteDecreaseOrderGasLimit(gasLimit, swapCount);
break;
default:
throw new ArgumentOutOfRangeException(nameof(tradeType), tradeType, null);
}
return new BigInteger();
}
private BigInteger EstimateExecuteIncreaseOrderGasLimit(GmxV2GasLimit gasLimit, int? swapCount = null,
BigInteger? callbackGasLimit = null)
{
var gasPerSwap = gasLimit.SingleSwap;
var swapsCount = new BigInteger(swapCount ?? 0);
var totalGasLimit = gasLimit.IncreaseOrder + gasPerSwap * swapsCount +
(callbackGasLimit ?? BigInteger.Zero);
return totalGasLimit;
}
private BigInteger EstimateExecuteDecreaseOrderGasLimit(GmxV2GasLimit gasLimit, int swapCount,
BigInteger? callbackGasLimit = null, GmxV2Enums.DecreasePositionSwapType? decreaseSwapType = null)
{
var gasPerSwap = gasLimit.SingleSwap;
var swapsCount = new BigInteger(swapCount);
if (decreaseSwapType != GmxV2Enums.DecreasePositionSwapType.NoSwap)
{
swapsCount += 1;
}
var totalGasLimit = gasLimit.DecreaseOrder + gasPerSwap * swapsCount + (callbackGasLimit ?? BigInteger.Zero);
return totalGasLimit;
}
public async Task<Trade> GetTrade(Web3 web3, string publicAddress, Enums.Ticker ticker)
{
var position = await GetGmxPositionsV2(web3, publicAddress);
var positionsPerTicker =
position.First(p => GmxV2Helpers.SameAddress(p.MarketAddress, GmxV2Helpers.GetMarketAddress(ticker)));
return GmxV2Mappers.Map(positionsPerTicker, ticker);
}
private async Task<List<GmxV2Position>> GetGmxPositionsV2(Web3 web3, string account)
{
var marketsInfoData = await GetMarketInfosAsync(web3);
var tokensData = await GetTokensData(web3, true);
var (marketKeys, marketPrices, allPositionsKeys) = GetKeysAndPrices(marketsInfoData, tokensData, account);
var getAccountPositionInfoListFunction = new GetAccountPositionInfoListFunction
{
DataStore = Arbitrum.AddressV2.DataStore,
ReferralStorage = Arbitrum.AddressV2.ReferralStorage,
Account = account,
Markets = marketKeys,
MarketPrices = GmxV2Mappers.Map(marketPrices),
UiFeeReceiver = Arbitrum.Address.Zero,
Start = new BigInteger(0),
End = new BigInteger(1000)
}.GetCallData();
var getPositionCall = new Call()
{
Target = Arbitrum.AddressV2.SyntheticsReader,
CallData = getAccountPositionInfoListFunction
};
var aggregateFunction = new AggregateFunction();
aggregateFunction.Calls = new List<Call>();
aggregateFunction.Calls.Add(getPositionCall);
var queryHandler = web3.Eth.GetContractQueryHandler<AggregateFunction>();
var returnCalls = await queryHandler
.QueryDeserializingToObjectAsync<AggregateOutputDTO>(aggregateFunction, Arbitrum.AddressV2.Multicall)
.ConfigureAwait(false);
var hexResult = returnCalls.ReturnData[0].ToHex();
var positions = new GetAccountPositionInfoListOutputDTO().DecodeOutput(hexResult).ReturnValue1;
var result = new List<GmxV2Position>();
foreach (var positionInfo in positions)
{
var position = positionInfo.Position;
var fees = positionInfo.Fees;
var addresses = position.Addresses;
var numbers = position.Numbers;
var flags = position.Flags;
var marketInfo =
marketsInfoData.First(m => GmxV2Helpers.SameAddress(m.Market.MarketToken, addresses.Market));
var tokenData = TokenV2Service.GetByTicker(Enums.Ticker.USDC);
if (numbers.IncreasedAtTime == 0)
{
continue;
}
var positionKey = GmxV2Helpers.GetPositionKey(addresses.Account, addresses.Market,
addresses.CollateralToken, flags.IsLong);
var contractPositionKey = _encoder.HashData([
("address", addresses.Account),
("address", addresses.Market),
("address", addresses.CollateralToken),
("bool", flags.IsLong)
]);
var gmxPosition = new GmxV2Position
{
Key = positionKey,
ContractKey = contractPositionKey.ToString(),
Account = addresses.Account,
MarketAddress = addresses.Market,
CollateralTokenAddress = addresses.CollateralToken,
SizeInUsd = numbers.SizeInUsd,
SizeInTokens = numbers.SizeInTokens,
CollateralAmount = numbers.CollateralAmount,
IncreasedAtTime = numbers.IncreasedAtTime,
DecreasedAtTime = numbers.DecreasedAtTime,
IsLong = flags.IsLong,
PendingBorrowingFeesUsd = fees.Borrowing.BorrowingFeeUsd,
FundingFeeAmount = fees.Funding.FundingFeeAmount,
ClaimableLongTokenAmount = fees.Funding.ClaimableLongTokenAmount,
ClaimableShortTokenAmount = fees.Funding.ClaimableShortTokenAmount,
MarketInfo = marketInfo,
TokenData = tokenData
};
result.Add(gmxPosition);
}
return result;
}
private (List<string> marketKeys, List<MarketPrices> marketPrices, List<string> allPositionsKeys) GetKeysAndPrices(
List<GmxMarketInfo> marketsInfoData, List<GmxTokenData> tokensData, string account)
{
var values = (
marketKeys: new List<string>(),
marketPrices: new List<MarketPrices>(),
allPositionsKeys: new List<string>()
);
if (string.IsNullOrEmpty(account) || marketsInfoData == null || tokensData == null)
{
return values;
}
foreach (var market in marketsInfoData)
{
var marketPrices = GmxHelpers.GetContractMarketPrices(tokensData, market.Market);
if (marketPrices == null || market.Market.IsSpotOnly)
{
continue;
}
values.marketKeys.Add(market.Market.MarketToken);
values.marketPrices.Add(marketPrices);
var collaterals = market.Market.IsSameCollaterals
? new List<string> { market.Market.LongToken }
: new List<string> { market.Market.LongToken, market.Market.ShortToken };
foreach (var collateralAddress in collaterals)
{
foreach (var isLong in new[] { true, false })
{
var positionKey = GetPositionKeyV2(account, market.Market.MarketToken, collateralAddress, isLong);
values.allPositionsKeys.Add(positionKey);
}
}
}
return values;
}
// Helper Methods
private static string GetPositionKeyV2(string account, string market, string collateralToken, bool isLong)
{
return $"{account}-{market}-{collateralToken}-{(isLong ? "true" : "false")}";
}
}