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
This commit is contained in:
54
src/Managing.Infrastructure.Web3/EvmBase.cs
Normal file
54
src/Managing.Infrastructure.Web3/EvmBase.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Numerics;
|
||||
using Managing.Core;
|
||||
using Nethereum.Contracts.Standards.ERC20;
|
||||
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
|
||||
using Nethereum.Hex.HexTypes;
|
||||
using Nethereum.Util;
|
||||
using Nethereum.Web3;
|
||||
|
||||
namespace Managing.Infrastructure.Evm;
|
||||
|
||||
public static class EvmBase
|
||||
{
|
||||
public static async Task<bool> ApproveToken(Web3 web3, string publicAddress, string contractAddress, string spender,
|
||||
BigInteger amount = default)
|
||||
{
|
||||
var input = new ApproveFunction
|
||||
{
|
||||
Spender = spender,
|
||||
Value = UnitConversion.Convert.ToWei(amount),
|
||||
FromAddress = publicAddress
|
||||
};
|
||||
var txHandler = web3.Eth
|
||||
.GetContractTransactionHandler<ApproveFunction>();
|
||||
|
||||
var result = await txHandler.SendRequestAndWaitForReceiptAsync(contractAddress, input);
|
||||
return result.Status == new HexBigInteger(1);
|
||||
}
|
||||
|
||||
public static async Task<BigInteger> GetAllowance(Web3 web3, string publicAddress, string contractAddress)
|
||||
{
|
||||
var input = new AllowanceFunction
|
||||
{
|
||||
Owner = publicAddress,
|
||||
Spender = contractAddress
|
||||
};
|
||||
|
||||
var contract = new ERC20ContractService(web3.Eth, contractAddress);
|
||||
var allowance = await contract.AllowanceQueryAsync(input);
|
||||
return allowance;
|
||||
}
|
||||
|
||||
public static async Task<DateTime> GetBlockDate(Web3 web3, HexBigInteger blockNumber)
|
||||
{
|
||||
var block = await web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(blockNumber);
|
||||
var date = DateHelpers.GetFromUnixTimestamp((int)block.Timestamp.Value);
|
||||
return date;
|
||||
}
|
||||
|
||||
public static async Task<BigInteger> GetGasPrice(Web3 web3)
|
||||
{
|
||||
var gasPrice = await web3.Eth.GasPrice.SendRequestAsync();
|
||||
return gasPrice.Value;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ using Managing.Domain.Evm;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Abstractions;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx;
|
||||
using Managing.Infrastructure.Evm.Models;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
using Managing.Infrastructure.Evm.Referentials;
|
||||
using Managing.Infrastructure.Evm.Services;
|
||||
using Managing.Infrastructure.Evm.Services.Gmx;
|
||||
@@ -21,6 +22,10 @@ using Nethereum.Hex.HexTypes;
|
||||
using Nethereum.Signer;
|
||||
using Nethereum.Web3;
|
||||
using static Managing.Common.Enums;
|
||||
using BalanceOfFunction = Nethereum.Contracts.Standards.ERC20.ContractDefinition.BalanceOfFunction;
|
||||
using BalanceOfOutputDTO = Nethereum.Contracts.Standards.ERC20.ContractDefinition.BalanceOfOutputDTO;
|
||||
using Chain = Managing.Domain.Evm.Chain;
|
||||
using TransferEventDTO = Nethereum.Contracts.Standards.ERC721.ContractDefinition.TransferEventDTO;
|
||||
|
||||
namespace Managing.Infrastructure.Evm;
|
||||
|
||||
@@ -31,6 +36,7 @@ public class EvmManager : IEvmManager
|
||||
private readonly string _password = "!StrongPassword94";
|
||||
private readonly IEnumerable<ISubgraphPrices> _subgraphs;
|
||||
private Dictionary<string, Dictionary<string, decimal>> _geckoPrices;
|
||||
private readonly GmxV2Service _gmxV2Service;
|
||||
|
||||
public EvmManager(IEnumerable<ISubgraphPrices> subgraphs)
|
||||
{
|
||||
@@ -41,6 +47,7 @@ public class EvmManager : IEvmManager
|
||||
_geckoPrices = _geckoPrices != null && _geckoPrices.Any()
|
||||
? _geckoPrices
|
||||
: new Dictionary<string, Dictionary<string, decimal>>();
|
||||
_gmxV2Service = new GmxV2Service();
|
||||
SetupPrices();
|
||||
}
|
||||
|
||||
@@ -133,7 +140,7 @@ public class EvmManager : IEvmManager
|
||||
return holders;
|
||||
}
|
||||
|
||||
public async Task<List<EventLog<Nethereum.Contracts.Standards.ERC721.ContractDefinition.TransferEventDTO>>>
|
||||
public async Task<List<EventLog<TransferEventDTO>>>
|
||||
GetNftEvent(string contractAddress, string tokenOwner)
|
||||
{
|
||||
return await NftService.GetNftEvent(_web3, tokenOwner, contractAddress);
|
||||
@@ -185,7 +192,7 @@ public class EvmManager : IEvmManager
|
||||
return wallet.GetAccount(0).Address;
|
||||
}
|
||||
|
||||
public async Task<EvmBalance> GetEtherBalance(Domain.Evm.Chain chain, string account)
|
||||
public async Task<EvmBalance> GetEtherBalance(Chain chain, string account)
|
||||
{
|
||||
var web3 = new Web3(chain.RpcUrl);
|
||||
var etherBalance = Web3.Convert.FromWei(await web3.Eth.GetBalance.SendRequestAsync(account));
|
||||
@@ -195,12 +202,9 @@ public class EvmManager : IEvmManager
|
||||
{ Balance = etherBalance, Price = etherPrice, TokenName = "ETH", Value = etherBalance * etherPrice };
|
||||
}
|
||||
|
||||
public async Task<List<EvmBalance>> GetAllBalances(Domain.Evm.Chain chain, string publicAddress)
|
||||
public async Task<List<EvmBalance>> GetAllBalances(Chain chain, string publicAddress)
|
||||
{
|
||||
var balances = new List<EvmBalance>();
|
||||
|
||||
|
||||
var web3 = new Web3(chain.RpcUrl);
|
||||
SetupPrices();
|
||||
foreach (var ticker in Enum.GetValues<Ticker>())
|
||||
{
|
||||
@@ -221,13 +225,6 @@ public class EvmManager : IEvmManager
|
||||
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;
|
||||
}
|
||||
@@ -272,7 +269,7 @@ public class EvmManager : IEvmManager
|
||||
return evmBalance;
|
||||
}
|
||||
|
||||
public async Task<List<EvmBalance>> GetBalances(Domain.Evm.Chain chain, int page, int pageSize,
|
||||
public async Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize,
|
||||
string publicAddress)
|
||||
{
|
||||
var callList = new List<IMulticallInputOutput>();
|
||||
@@ -338,6 +335,8 @@ public class EvmManager : IEvmManager
|
||||
{
|
||||
// TODO : Handle error
|
||||
// No enable to reach rpc
|
||||
|
||||
var test = ex.Message;
|
||||
}
|
||||
|
||||
return evmTokens.Select(e => e.Balance).ToList();
|
||||
@@ -433,23 +432,15 @@ public class EvmManager : IEvmManager
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ApproveTicker(string publicAddress, string privateKey, Ticker ticker)
|
||||
public async Task<BigInteger> GetAllowance(string publicAddress, 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;
|
||||
}
|
||||
var contractAddress = TokenService.GetContractAddress(ticker);
|
||||
var allowance = await EvmBase.GetAllowance(_web3, publicAddress, contractAddress);
|
||||
return allowance;
|
||||
}
|
||||
|
||||
public async Task<bool> Send(
|
||||
Domain.Evm.Chain chain,
|
||||
Chain chain,
|
||||
Ticker ticker,
|
||||
decimal amount,
|
||||
string publicAddress,
|
||||
@@ -465,10 +456,8 @@ public class EvmManager : IEvmManager
|
||||
{
|
||||
return await SendEth(amount, receiverAddress, web3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await SendToken(ticker, amount, publicAddress, receiverAddress, web3);
|
||||
}
|
||||
|
||||
return await SendToken(ticker, amount, publicAddress, receiverAddress, web3);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -515,7 +504,9 @@ public class EvmManager : IEvmManager
|
||||
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);
|
||||
// return await GmxService.CancelOrders(web3, account.Key, ticker);
|
||||
var service = new GmxV2Service();
|
||||
return await service.CancelOrders(web3, account.Key, ticker);
|
||||
}
|
||||
|
||||
public async Task<Trade> IncreasePosition(
|
||||
@@ -533,7 +524,8 @@ public class EvmManager : IEvmManager
|
||||
Trade trade = null;
|
||||
try
|
||||
{
|
||||
trade = await GmxService.IncreasePosition(web3, account.Key, ticker, direction, price, quantity, leverage);
|
||||
trade = await _gmxV2Service.IncreasePosition(web3, account.Key, ticker, direction, price, quantity,
|
||||
leverage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -558,7 +550,8 @@ public class EvmManager : IEvmManager
|
||||
Trade trade = null;
|
||||
try
|
||||
{
|
||||
trade = await GmxService.DecreasePosition(web3, account.Key, ticker, direction, price, quantity, leverage);
|
||||
trade = await _gmxV2Service.DecreasePosition(web3, account.Key, ticker, direction, price, quantity,
|
||||
leverage ?? 1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -575,7 +568,7 @@ public class EvmManager : IEvmManager
|
||||
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
|
||||
var web3 = new Web3(wallet, chain.RpcUrl);
|
||||
|
||||
Trade trade = null;
|
||||
Trade trade;
|
||||
try
|
||||
{
|
||||
trade = await GmxService.DecreaseOrder(web3, tradeType, account.Key, ticker, direction, price, quantity,
|
||||
@@ -599,7 +592,7 @@ public class EvmManager : IEvmManager
|
||||
{
|
||||
var chain = ChainService.GetChain(chainName);
|
||||
var web3 = new Web3(chain.RpcUrl);
|
||||
return await GmxService.GetTrade(web3, reference, ticker);
|
||||
return await _gmxV2Service.GetTrade(web3, reference, ticker);
|
||||
}
|
||||
|
||||
public async Task<List<FundingRate>> GetFundingRates()
|
||||
@@ -634,8 +627,25 @@ public class EvmManager : IEvmManager
|
||||
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);
|
||||
// var orders = await GmxService.GetOrders(web3, account.Key, ticker);
|
||||
var orders = await _gmxV2Service.GetOrders(web3, account.Key, ticker);
|
||||
|
||||
return GmxHelpers.Map(orders, ticker);
|
||||
return GmxV2Mappers.Map(orders);
|
||||
}
|
||||
|
||||
public async Task<bool> SetAllowance(Account account, Ticker ticker, BigInteger amount)
|
||||
{
|
||||
var web3 = BuildWeb3ForAccount(account);
|
||||
var contractAddress = TokenService.GetContractAddress(ticker);
|
||||
var approval = await EvmBase.ApproveToken(web3, account.Key, contractAddress,
|
||||
Arbitrum.AddressV2.SyntheticsRouter, amount);
|
||||
return approval;
|
||||
}
|
||||
|
||||
private Web3 BuildWeb3ForAccount(Account account)
|
||||
{
|
||||
var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
|
||||
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
|
||||
return new Web3(wallet, chain.RpcUrl);
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,9 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
|
||||
<PackageReference Include="NBitcoin" Version="7.0.36"/>
|
||||
<PackageReference Include="Nethereum.HdWallet" Version="4.20.0"/>
|
||||
<PackageReference Include="Nethereum.Hex" Version="4.21.3"/>
|
||||
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="4.20.0"/>
|
||||
<PackageReference Include="Nethereum.Web3" Version="4.21.2"/>
|
||||
<PackageReference Include="Nethereum.Hex" Version="4.28.0"/>
|
||||
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="4.28.0"/>
|
||||
<PackageReference Include="Nethereum.Web3" Version="4.28.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Managing.Domain.Evm;
|
||||
namespace Managing.Infrastructure.Evm.Models;
|
||||
|
||||
public class GeckoToken
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Numerics;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx;
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
|
||||
public class GmxOrder
|
||||
{
|
||||
@@ -16,4 +16,4 @@ public class GmxOrder
|
||||
public bool TriggerAboveThreshold { get; internal set; }
|
||||
public GmxOrderType Type { get; internal set; }
|
||||
public BigInteger Index { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx;
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
|
||||
public class GmxOrderIndexes
|
||||
{
|
||||
public int SwapIndex { get; set; }
|
||||
public int IncreaseIndex { get; set; }
|
||||
public int DecreaseIndex { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx;
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
|
||||
public class GmxPosition
|
||||
{
|
||||
@@ -16,4 +16,4 @@ public class GmxPosition
|
||||
public BigInteger HasProfit { get; set; }
|
||||
public BigInteger Delta { get; set; }
|
||||
public BigInteger LastIncreasedTime { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx;
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
|
||||
public class GmxPrices
|
||||
{
|
||||
@@ -7,8 +7,6 @@ public class GmxPrices
|
||||
public int updatedAt { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class GmxOhlc
|
||||
{
|
||||
public int t { get; set; }
|
||||
@@ -16,4 +14,4 @@ public class GmxOhlc
|
||||
public double c { get; set; }
|
||||
public double h { get; set; }
|
||||
public double l { get; set; }
|
||||
}
|
||||
}
|
||||
31
src/Managing.Infrastructure.Web3/Models/Gmx/v2/GmxTicker.cs
Normal file
31
src/Managing.Infrastructure.Web3/Models/Gmx/v2/GmxTicker.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
public class GmxTicker
|
||||
{
|
||||
[JsonProperty("tokenAddress", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonPropertyName("tokenAddress")]
|
||||
public string TokenAddress { get; set; }
|
||||
|
||||
[JsonProperty("tokenSymbol", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonPropertyName("tokenSymbol")]
|
||||
public string TokenSymbol { get; set; }
|
||||
|
||||
[JsonProperty("minPrice", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonPropertyName("minPrice")]
|
||||
public string MinPrice { get; set; }
|
||||
|
||||
[JsonProperty("maxPrice", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonPropertyName("maxPrice")]
|
||||
public string MaxPrice { get; set; }
|
||||
|
||||
[JsonProperty("updatedAt", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public object UpdatedAt { get; set; }
|
||||
|
||||
[JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonPropertyName("timestamp")]
|
||||
public int Timestamp { get; set; }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Managing.ABI.GmxV2.Reader.ContractDefinition;
|
||||
using Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
public class GmxV2ExecutionFee
|
||||
{
|
||||
public string FeeUsd { get; set; }
|
||||
public BigInteger FeeAmount { get; set; }
|
||||
public GmxTokenData NativeToken { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
public class GmxV2GasLimit
|
||||
{
|
||||
public BigInteger DepositSingleToken { get; set; }
|
||||
public BigInteger DepositMultiToken { get; set; }
|
||||
public BigInteger WithdrawalMultiToken { get; set; }
|
||||
public BigInteger Shift { get; set; }
|
||||
public BigInteger SingleSwap { get; set; }
|
||||
public BigInteger SwapOrder { get; set; }
|
||||
public BigInteger IncreaseOrder { get; set; }
|
||||
public BigInteger DecreaseOrder { get; set; }
|
||||
public BigInteger EstimatedGasFeeBaseAmount { get; set; }
|
||||
public BigInteger EstimatedGasFeePerOraclePrice { get; set; }
|
||||
public BigInteger EstimatedFeeMultiplierFactor { get; set; }
|
||||
}
|
||||
66
src/Managing.Infrastructure.Web3/Models/Gmx/v2/GmxV2Order.cs
Normal file
66
src/Managing.Infrastructure.Web3/Models/Gmx/v2/GmxV2Order.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Numerics;
|
||||
using Managing.ABI.GmxV2.Reader.ContractDefinition;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
public class GmxV2Order
|
||||
{
|
||||
public byte[] Key { get; set; }
|
||||
public string Account { get; set; }
|
||||
public string Receiver { get; set; }
|
||||
public string CallbackContract { get; set; }
|
||||
public string MarketAddress { get; set; }
|
||||
public string InitialCollateralTokenAddress { get; set; }
|
||||
public List<string> SwapPath { get; set; }
|
||||
public BigInteger SizeDeltaUsd { get; set; }
|
||||
public BigInteger InitialCollateralDeltaAmount { get; set; }
|
||||
public BigInteger ContractTriggerPrice { get; set; }
|
||||
public BigInteger ContractAcceptablePrice { get; set; }
|
||||
public BigInteger ExecutionFee { get; set; }
|
||||
public BigInteger CallbackGasLimit { get; set; }
|
||||
public BigInteger MinOutputAmount { get; set; }
|
||||
public BigInteger UpdatedAtBlock { get; set; }
|
||||
public bool IsLong { get; set; }
|
||||
public bool ShouldUnwrapNativeToken { get; set; }
|
||||
public bool IsFrozen { get; set; }
|
||||
public GmxV2OrderType OrderType { get; set; }
|
||||
public GmxV2DecreasePositionSwapType DecreasePositionSwapType { get; set; }
|
||||
public Props Data { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
|
||||
public enum GmxV2DecreasePositionSwapType
|
||||
{
|
||||
NoSwap = 0,
|
||||
SwapPnlTokenToCollateralToken = 1,
|
||||
SwapCollateralTokenToPnlToken = 2
|
||||
}
|
||||
|
||||
public enum GmxV2OrderType
|
||||
{
|
||||
// the order will be cancelled if the minOutputAmount cannot be fulfilled
|
||||
MarketSwap = 0,
|
||||
|
||||
// @dev LimitSwap: swap token A to token B if the minOutputAmount can be fulfilled
|
||||
LimitSwap = 1,
|
||||
|
||||
// @dev MarketIncrease: increase position at the current market price
|
||||
// the order will be cancelled if the position cannot be increased at the acceptablePrice
|
||||
MarketIncrease = 2,
|
||||
|
||||
// @dev LimitIncrease: increase position if the triggerPrice is reached and the acceptablePrice can be fulfilled
|
||||
LimitIncrease = 3,
|
||||
|
||||
// @dev MarketDecrease: decrease position at the current market price
|
||||
// the order will be cancelled if the position cannot be decreased at the acceptablePrice
|
||||
MarketDecrease = 4,
|
||||
|
||||
// @dev LimitDecrease: decrease position if the triggerPrice is reached and the acceptablePrice can be fulfilled
|
||||
LimitDecrease = 5,
|
||||
|
||||
// @dev StopLossDecrease: decrease position if the triggerPrice is reached and the acceptablePrice can be fulfilled
|
||||
StopLossDecrease = 6,
|
||||
|
||||
// @dev Liquidation: allows liquidation of positions if the criteria for liquidation are met
|
||||
Liquidation = 7
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Numerics;
|
||||
using Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
public class GmxV2Position
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string? ContractKey { get; set; }
|
||||
public string Account { get; set; }
|
||||
public string MarketAddress { get; set; }
|
||||
public string CollateralTokenAddress { get; set; }
|
||||
public BigInteger SizeInUsd { get; set; }
|
||||
public BigInteger SizeInTokens { get; set; }
|
||||
public BigInteger CollateralAmount { get; set; }
|
||||
public BigInteger PendingBorrowingFeesUsd { get; set; }
|
||||
public BigInteger IncreasedAtTime { get; set; }
|
||||
public BigInteger DecreasedAtTime { get; set; }
|
||||
public bool IsLong { get; set; }
|
||||
public BigInteger FundingFeeAmount { get; set; }
|
||||
public BigInteger ClaimableLongTokenAmount { get; set; }
|
||||
public BigInteger ClaimableShortTokenAmount { get; set; }
|
||||
public bool? IsOpening { get; set; }
|
||||
public PendingPositionUpdate PendingUpdate { get; set; }
|
||||
public string Data { get; set; }
|
||||
public GmxMarketInfo MarketInfo { get; set; }
|
||||
public GmxToken TokenData { get; set; }
|
||||
}
|
||||
|
||||
public class PendingPositionUpdate
|
||||
{
|
||||
public bool IsIncrease { get; set; }
|
||||
public string PositionKey { get; set; }
|
||||
public BigInteger SizeDeltaUsd { get; set; }
|
||||
public BigInteger SizeDeltaInTokens { get; set; }
|
||||
public BigInteger CollateralDeltaAmount { get; set; }
|
||||
public int UpdatedAt { get; set; }
|
||||
public BigInteger UpdatedAtTime { get; set; }
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public class Arbitrum
|
||||
public const string LINK = "0xf97f4df75117a78c1a5a0dbb814af92458539fb4";
|
||||
public const string UNI = "0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0";
|
||||
|
||||
public const string USDC = "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8";
|
||||
public const string USDC = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
|
||||
public const string USDT = "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9";
|
||||
public const string DAI = "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1";
|
||||
public const string MIM = "0xFEa7a6a0B346362BF88A9e4A88416B77a57D6c2A";
|
||||
@@ -52,7 +52,7 @@ public class Arbitrum
|
||||
|
||||
public const string UniswapGmxEthPool = "0x80A9ae39310abf666A87C743d6ebBD0E8C42158E";
|
||||
|
||||
public static string Zero = "0x0000000000000000000000000000000000000000";
|
||||
public const string Zero = "0x0000000000000000000000000000000000000000";
|
||||
}
|
||||
|
||||
public class AddressV2
|
||||
@@ -60,12 +60,14 @@ public class Arbitrum
|
||||
public const string DataStore = "0xFD70de6b91282D8017aA4E741e9Ae325CAb992d8";
|
||||
public const string EventEmitter = "0xC8ee91A54287DB53897056e12D9819156D3822Fb";
|
||||
public const string SubaccountRouter = "0x9F48160eDc3Ad78F4cA0E3FDF54A75D8FB228452";
|
||||
public const string ExchangeRouter = "0x69C527fC77291722b52649E45c838e41be8Bf5d5";
|
||||
public const string ExchangeRouter = "0x900173A66dbD345006C51fA35fA3aB760FcD843b";
|
||||
public const string DepositVault = "0xF89e77e8Dc11691C9e8757e84aaFbCD8A67d7A55";
|
||||
public const string WithdrawalVault = "0x0628D46b5D145f183AdB6Ef1f2c97eD1C4701C55";
|
||||
public const string OrderVault = "0x31eF83a530Fde1B38EE9A18093A333D8Bbbc40D5";
|
||||
public const string Reader = "0x5Ca84c34a381434786738735265b9f3FD814b824";
|
||||
public const string Reader = "0x0537C767cDAC0726c76Bb89e92904fe28fd02fE1";
|
||||
public const string SyntheticsRouter = "0x7452c558d45f8afC8c83dAe62C3f8A5BE19c71f6";
|
||||
public const string Multicall = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
||||
public const string SyntheticsReader = "0x0537C767cDAC0726c76Bb89e92904fe28fd02fE1";
|
||||
public const string Multicall = "0x842ec2c7d803033edf55e478f461fc547bc54eb2";
|
||||
public const string ReferralStorage = "0xe6fab3f0c7199b0d34d7fbe83394fc0e0d06e99d";
|
||||
}
|
||||
}
|
||||
17
src/Managing.Infrastructure.Web3/Referentials/GmxV2Enums.cs
Normal file
17
src/Managing.Infrastructure.Web3/Referentials/GmxV2Enums.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Managing.Infrastructure.Evm.Referentials;
|
||||
|
||||
public static class GmxV2Enums
|
||||
{
|
||||
public enum TradeType
|
||||
{
|
||||
Swap,
|
||||
Increase,
|
||||
Decrease
|
||||
}
|
||||
|
||||
public enum DecreasePositionSwapType
|
||||
{
|
||||
NoSwap,
|
||||
Swap
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ 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 =
|
||||
"https://convincing-smart-arm.arbitrum-mainnet.quiknode.pro/561ad3fa1db431a2c728c2fdb1a62e8f94acf703";
|
||||
|
||||
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";
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
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.Signer;
|
||||
using Nethereum.Web3;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -62,7 +61,7 @@ public static class GmxHelpers
|
||||
{
|
||||
decimal priceBasisPoints;
|
||||
var basisPointDivisor = 10000m;
|
||||
var allowedSlippage = 34m;
|
||||
var allowedSlippage = 30m;
|
||||
var toDecimal = 30;
|
||||
|
||||
if (isLong)
|
||||
@@ -81,32 +80,6 @@ public static class GmxHelpers
|
||||
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) ||
|
||||
@@ -145,9 +118,9 @@ public static class GmxHelpers
|
||||
|
||||
public static string GetRandomAddress()
|
||||
{
|
||||
var ecKey = Nethereum.Signer.EthECKey.GenerateKey();
|
||||
var ecKey = EthECKey.GenerateKey();
|
||||
var privateKey = ecKey.GetPrivateKeyAsBytes().ToHex();
|
||||
var account = new Nethereum.Signer.EthECKey(privateKey).GetPublicAddress();
|
||||
var account = new EthECKey(privateKey).GetPublicAddress();
|
||||
return account;
|
||||
}
|
||||
|
||||
@@ -168,15 +141,15 @@ public static class GmxHelpers
|
||||
private static MarketPrice ConvertToContractTokenPrice(MarketPrice marketPrice, int tokenDecimals)
|
||||
{
|
||||
var price = new MarketPrice();
|
||||
price.Min = ConvertToContractPrice(tokenDecimals, marketPrice.Min);
|
||||
price.Max = ConvertToContractPrice(tokenDecimals, marketPrice.Max);
|
||||
price.Min = ConvertToContractPrice(marketPrice.Min, tokenDecimals);
|
||||
price.Max = ConvertToContractPrice(marketPrice.Max, tokenDecimals);
|
||||
return price;
|
||||
}
|
||||
|
||||
private static BigInteger ConvertToContractPrice(decimal price, BigInteger tokenDecimals)
|
||||
private static BigInteger ConvertToContractPrice(BigInteger price, decimal 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));
|
||||
BigInteger scaledPrice = price * new BigInteger((decimal)Math.Pow(10, (double)tokenDecimals));
|
||||
|
||||
// Return the scaled price directly since it's already adjusted for token decimals.
|
||||
return scaledPrice;
|
||||
@@ -257,4 +230,19 @@ public static class GmxHelpers
|
||||
parts[0] = int.Parse(parts[0]).ToString("N0", CultureInfo.InvariantCulture);
|
||||
return string.Join(".", parts);
|
||||
}
|
||||
|
||||
public static BigInteger? ConvertToUsd(BigInteger? tokenAmount, int? tokenDecimals, BigInteger? price)
|
||||
{
|
||||
if (tokenAmount == null || tokenDecimals == null || price == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (tokenAmount.Value * price.Value) / ExpandDecimals(1, tokenDecimals.Value);
|
||||
}
|
||||
|
||||
private static BigInteger ExpandDecimals(int value, int decimals)
|
||||
{
|
||||
return BigInteger.Multiply(value, BigInteger.Pow(10, decimals));
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
using Managing.ABI.GmxV2.Reader.ContractDefinition;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
using Managing.Infrastructure.Evm.Referentials;
|
||||
using Nethereum.Util;
|
||||
using Nethereum.Web3;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -184,7 +186,7 @@ public static class GmxMappers
|
||||
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 isSpotOnly = infos.Market.IndexToken == AddressUtil.ZERO_ADDRESS;
|
||||
var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
|
||||
isSpotOnly);
|
||||
var symbol = indexToken.Symbol;
|
||||
@@ -211,7 +213,7 @@ public static class GmxMappers
|
||||
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 isSpotOnly = infos.IndexToken == AddressUtil.ZERO_ADDRESS;
|
||||
var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
|
||||
isSpotOnly);
|
||||
|
||||
@@ -245,4 +247,46 @@ public static class GmxMappers
|
||||
ImpactPoolAmount = response.ReturnValue2.ImpactPoolAmount
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
GmxHelpers.GetTradeType(order.IsLong, order.TriggerAboveThreshold),
|
||||
ticker,
|
||||
Convert.ToDecimal(order.SizeDelta),
|
||||
Convert.ToDecimal(order.TriggerPrice),
|
||||
null,
|
||||
"", ""
|
||||
);
|
||||
|
||||
return trade;
|
||||
}
|
||||
|
||||
public static List<FundingRate> Map(GmxMarketInfo market)
|
||||
{
|
||||
var longApr =
|
||||
GmxV2Helpers.GetFundingFactorPerPeriod(market, true, GmxV2Helpers.Periods[Timeframe.OneHour]) * 100;
|
||||
var shortApr =
|
||||
GmxV2Helpers.GetFundingFactorPerPeriod(market, false, GmxV2Helpers.Periods[Timeframe.OneHour]) * 100;
|
||||
var longFundingRate = new FundingRate()
|
||||
{
|
||||
Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
|
||||
Rate = GmxHelpers.FormatAmount(longApr, 30, 4),
|
||||
Direction = TradeDirection.Long
|
||||
};
|
||||
var shortFundingRate = new FundingRate()
|
||||
{
|
||||
Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
|
||||
Rate = GmxHelpers.FormatAmount(shortApr, 30, 4),
|
||||
Direction = TradeDirection.Short
|
||||
};
|
||||
return new List<FundingRate>() { longFundingRate, shortFundingRate };
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
using System.Numerics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v1;
|
||||
using Managing.Infrastructure.Evm.Referentials;
|
||||
using Managing.Tools.ABI.OrderBook;
|
||||
using Managing.Tools.ABI.OrderBook.ContractDefinition;
|
||||
using Managing.Tools.ABI.OrderBookReader;
|
||||
using Managing.Tools.ABI.OrderBookReader.ContractDefinition;
|
||||
using Managing.Tools.ABI.PositionRouter;
|
||||
using Managing.Tools.ABI.PositionRouter.ContractDefinition;
|
||||
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.Router;
|
||||
using Managing.Tools.Router.ContractDefinition;
|
||||
using Managing.Tools.ABI.Router;
|
||||
using Managing.Tools.ABI.Router.ContractDefinition;
|
||||
using Nethereum.Contracts.Standards.ERC20;
|
||||
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
|
||||
using Nethereum.Util;
|
||||
using Nethereum.Web3;
|
||||
using static Managing.Common.Enums;
|
||||
using ApproveFunction = Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
@@ -25,7 +26,7 @@ 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)
|
||||
public static async 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))
|
||||
@@ -44,11 +45,11 @@ public static class GmxService
|
||||
foreach (var ticker in tickers)
|
||||
{
|
||||
var conntractAddress = TokenService.GetContractAddress(ticker);
|
||||
await ApproveToken(web3, publicAddress, conntractAddress);
|
||||
await EvmBase.ApproveToken(web3, publicAddress, conntractAddress, publicAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public async static Task<bool> IsPluginAdded(Web3 web3, string publicAddress, string pluginAddress)
|
||||
public static async Task<bool> IsPluginAdded(Web3 web3, string publicAddress, string pluginAddress)
|
||||
{
|
||||
var router = new RouterService(web3, Arbitrum.Address.Router);
|
||||
var function = new ApprovedPluginsFunction
|
||||
@@ -61,16 +62,6 @@ public static class GmxService
|
||||
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)
|
||||
{
|
||||
@@ -87,7 +78,7 @@ public static class GmxService
|
||||
|
||||
if (allowance.IsZero) return false;
|
||||
|
||||
var approveQuery = new Nethereum.Contracts.Standards.ERC20.ContractDefinition.ApproveFunction
|
||||
var approveQuery = new ApproveFunction
|
||||
{
|
||||
FromAddress = publicAddress,
|
||||
Spender = Arbitrum.Address.Router,
|
||||
@@ -98,7 +89,7 @@ public static class GmxService
|
||||
return true;
|
||||
}
|
||||
|
||||
public async static Task<Trade> IncreasePosition(Web3 web3, string publicAddress, Ticker ticker,
|
||||
public static async Task<Trade> IncreasePosition(Web3 web3, string publicAddress, Ticker ticker,
|
||||
TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
|
||||
{
|
||||
var quantityLeveraged = GmxHelpers.GetQuantityForLeverage(quantity, leverage);
|
||||
@@ -150,7 +141,7 @@ public static class GmxService
|
||||
return trade;
|
||||
}
|
||||
|
||||
public async static Task<Trade> DecreasePosition(Web3 web3, string publicAddress, Ticker ticker,
|
||||
public static async Task<Trade> DecreasePosition(Web3 web3, string publicAddress, Ticker ticker,
|
||||
TradeDirection direction, decimal price, decimal quantity, decimal? leverage)
|
||||
{
|
||||
var trade = new Trade(DateTime.UtcNow,
|
||||
@@ -270,7 +261,7 @@ public static class GmxService
|
||||
return trade;
|
||||
}
|
||||
|
||||
public async static Task<bool> CancelOrders(Web3 web3, string publicAddress, Ticker ticker)
|
||||
public static async Task<bool> CancelOrders(Web3 web3, string publicAddress, Ticker ticker)
|
||||
{
|
||||
var orderBook = new OrderBookService(web3, Arbitrum.Address.OrderBook);
|
||||
var orders = await GetOrders(web3, publicAddress, ticker);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Numerics;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Nethereum.ABI;
|
||||
using Nethereum.Util;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
public class GmxV2Encoder
|
||||
{
|
||||
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(List<(string dataType, object dataValues)> datas)
|
||||
{
|
||||
var key = GenerateKey(datas);
|
||||
|
||||
if (_dataCache.TryGetValue(key, out byte[] cachedHash))
|
||||
{
|
||||
return cachedHash;
|
||||
}
|
||||
|
||||
var abiValues = new List<ABIValue>();
|
||||
foreach (var data in datas)
|
||||
{
|
||||
var abiValue = new ABIValue(data.dataType, data.dataValues);
|
||||
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 List<(string dataType, object dataValues)>() { ("string", input) });
|
||||
_stringCache.Set(input, hash, _absoluteExpiration);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private string GenerateKey(List<(string dataType, object dataValues)> datas)
|
||||
{
|
||||
var combined = new List<string>();
|
||||
foreach (var data in datas)
|
||||
{
|
||||
combined.Add(
|
||||
$"{data.dataType}:{(data.dataValues is BigInteger bigInt ? bigInt.ToString() : data.dataValues)}");
|
||||
}
|
||||
|
||||
return string.Join(",", combined);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,209 @@
|
||||
using System.Numerics;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Nethereum.ABI;
|
||||
using Nethereum.Util;
|
||||
using Managing.Common;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
using Nethereum.Web3;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
public class GmxV2Helpers
|
||||
public static 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)
|
||||
public static BigInteger GetFundingFactorPerPeriod(GmxMarketInfo marketInfo, bool isLong, int periodInSeconds)
|
||||
{
|
||||
var key = GenerateKey(dataTypes, dataValues);
|
||||
var fundingFactorPerSecond = marketInfo.Market.FundingFactorPerSecond;
|
||||
var longsPayShorts = marketInfo.Market.LongsPayShorts;
|
||||
var longInterestUsd = marketInfo.Infos.LongInterestUsd;
|
||||
var shortInterestUsd = marketInfo.Infos.ShortInterestUsd;
|
||||
|
||||
if (_dataCache.TryGetValue(key, out byte[] cachedHash))
|
||||
var isLargerSide = isLong ? longsPayShorts : !longsPayShorts;
|
||||
|
||||
BigInteger factorPerSecond;
|
||||
|
||||
if (isLargerSide)
|
||||
{
|
||||
return cachedHash;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
return factorPerSecond * periodInSeconds;
|
||||
}
|
||||
|
||||
|
||||
public byte[] HashString(string input)
|
||||
private static BigInteger ApplyFactor(BigInteger ratio, BigInteger fundingFactorPerSecond)
|
||||
{
|
||||
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;
|
||||
// Implement the logic for applying the factor based on the ratio
|
||||
// This is a placeholder implementation
|
||||
return BigInteger.Multiply(ratio, fundingFactorPerSecond) / Precision;
|
||||
}
|
||||
|
||||
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])}");
|
||||
}
|
||||
private static readonly BigInteger Precision = BigInteger.Pow(10, 18);
|
||||
|
||||
return string.Join(",", combined);
|
||||
public static 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 }
|
||||
};
|
||||
|
||||
public static string GetMarketAddress(Enums.Ticker ticker)
|
||||
{
|
||||
// Switch statement to return the market address based on the ticker
|
||||
switch (ticker)
|
||||
{
|
||||
case Enums.Ticker.BTC: return Constants.GMX.Markets.BTCUSDC;
|
||||
case Enums.Ticker.ETH: return Constants.GMX.Markets.ETHUSDC;
|
||||
case Enums.Ticker.LINK: return Constants.GMX.Markets.LINKUSD;
|
||||
case Enums.Ticker.PEPE: return Constants.GMX.Markets.PEPEUSD;
|
||||
case Enums.Ticker.SOL: return Constants.GMX.Markets.SOLUSD;
|
||||
case Enums.Ticker.UNI: return Constants.GMX.Markets.UNIUSD;
|
||||
case Enums.Ticker.SHIB: return Constants.GMX.Markets.SHIBUSD;
|
||||
case Enums.Ticker.AAVE: return Constants.GMX.Markets.AAVEUSD;
|
||||
case Enums.Ticker.OP: return Constants.GMX.Markets.OPUSD;
|
||||
case Enums.Ticker.APE: return Constants.GMX.Markets.APEUSD;
|
||||
case Enums.Ticker.GMX: return Constants.GMX.Markets.GMXUSD;
|
||||
case Enums.Ticker.ARB: return Constants.GMX.Markets.ARBUSD;
|
||||
case Enums.Ticker.NEAR: return Constants.GMX.Markets.NEARUSD;
|
||||
case Enums.Ticker.STX: return Constants.GMX.Markets.STXUSD;
|
||||
case Enums.Ticker.LTC: return Constants.GMX.Markets.LTCUSD;
|
||||
case Enums.Ticker.XRP: return Constants.GMX.Markets.XRPUSD;
|
||||
case Enums.Ticker.WIF: return Constants.GMX.Markets.WIFUSD;
|
||||
case Enums.Ticker.ORDI: return Constants.GMX.Markets.ORDIUSD;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(ticker), "Invalid ticker value.");
|
||||
}
|
||||
}
|
||||
|
||||
public static Enums.Ticker GetTicker(string marketAddress)
|
||||
{
|
||||
switch (marketAddress)
|
||||
{
|
||||
case Constants.GMX.Markets.BTCUSDC: return Enums.Ticker.BTC;
|
||||
case Constants.GMX.Markets.ETHUSDC: return Enums.Ticker.ETH;
|
||||
case Constants.GMX.Markets.LINKUSD: return Enums.Ticker.LINK;
|
||||
case Constants.GMX.Markets.PEPEUSD: return Enums.Ticker.PEPE;
|
||||
case Constants.GMX.Markets.SOLUSD: return Enums.Ticker.SOL;
|
||||
case Constants.GMX.Markets.UNIUSD: return Enums.Ticker.UNI;
|
||||
case Constants.GMX.Markets.SHIBUSD: return Enums.Ticker.SHIB;
|
||||
case Constants.GMX.Markets.AAVEUSD: return Enums.Ticker.AAVE;
|
||||
case Constants.GMX.Markets.OPUSD: return Enums.Ticker.OP;
|
||||
case Constants.GMX.Markets.APEUSD: return Enums.Ticker.APE;
|
||||
case Constants.GMX.Markets.GMXUSD: return Enums.Ticker.GMX;
|
||||
case Constants.GMX.Markets.ARBUSD: return Enums.Ticker.ARB;
|
||||
case Constants.GMX.Markets.NEARUSD: return Enums.Ticker.NEAR;
|
||||
case Constants.GMX.Markets.STXUSD: return Enums.Ticker.STX;
|
||||
case Constants.GMX.Markets.LTCUSD: return Enums.Ticker.LTC;
|
||||
case Constants.GMX.Markets.XRPUSD: return Enums.Ticker.XRP;
|
||||
case Constants.GMX.Markets.WIFUSD: return Enums.Ticker.WIF;
|
||||
case Constants.GMX.Markets.ORDIUSD: return Enums.Ticker.ORDI;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(marketAddress), "Invalid market address.");
|
||||
}
|
||||
}
|
||||
|
||||
public static Enums.TradeType GetTradeType(GmxV2OrderType orderType)
|
||||
{
|
||||
return orderType switch
|
||||
{
|
||||
GmxV2OrderType.LimitIncrease => Enums.TradeType.Limit,
|
||||
GmxV2OrderType.LimitDecrease => Enums.TradeType.Limit,
|
||||
GmxV2OrderType.MarketIncrease => Enums.TradeType.Market,
|
||||
GmxV2OrderType.MarketDecrease => Enums.TradeType.Market,
|
||||
GmxV2OrderType.Liquidation => Enums.TradeType.Market,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(orderType), "Invalid order type.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the contract price to a decimal value.
|
||||
/// </summary>
|
||||
/// <param name="price"></param>
|
||||
/// <param name="tokenDecimals"></param>
|
||||
/// <param name="useTransform">Used to tranform the decimal since GMX is not using everytime the decimal from the MarketProps. Use true for prices and false for amounts</param>
|
||||
/// <returns></returns>
|
||||
public static decimal ParseContractPrice(BigInteger price, int tokenDecimals, bool useTransform = false)
|
||||
{
|
||||
if (useTransform)
|
||||
tokenDecimals = TransformValue(tokenDecimals);
|
||||
|
||||
return (decimal)price / (decimal)ExpandDecimals(1, tokenDecimals);
|
||||
}
|
||||
|
||||
public static BigInteger ConvertToContractPrice(decimal value, int tokenDecimals, bool useTransform = false)
|
||||
{
|
||||
if (useTransform)
|
||||
tokenDecimals = TransformValue(tokenDecimals);
|
||||
|
||||
return new BigInteger(value * (decimal)ExpandDecimals(1, tokenDecimals));
|
||||
}
|
||||
|
||||
public static BigInteger ExpandDecimals(int value, int decimals)
|
||||
{
|
||||
return BigInteger.Multiply(value, BigInteger.Pow(10, decimals));
|
||||
}
|
||||
|
||||
public static int TransformValue(int input)
|
||||
{
|
||||
// Apply the derived linear equation y = -x + 30
|
||||
return -input + 30;
|
||||
}
|
||||
|
||||
public static BigInteger EstimateOrderOraclePriceCount(int swapsCount)
|
||||
{
|
||||
return 3 + new BigInteger(swapsCount);
|
||||
}
|
||||
|
||||
public static bool SameAddress(string address, string address2)
|
||||
{
|
||||
return address.ToLower() == address2.ToLower();
|
||||
return string.Equals(address, address2, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public static BigInteger GetAcceptablePrice(decimal price, bool isLong)
|
||||
{
|
||||
var slippage = 0.003m; // 0.3% slippage
|
||||
var priceSlippage = price * slippage;
|
||||
var acceptablePrice = isLong ? price + priceSlippage : price - priceSlippage;
|
||||
|
||||
return Web3.Convert.ToWei(acceptablePrice, 22);
|
||||
}
|
||||
|
||||
public static BigInteger GetCollateralAmount(decimal amount, decimal leverage = 1)
|
||||
{
|
||||
return Web3.Convert.ToWei(amount * leverage, 30);
|
||||
}
|
||||
|
||||
public static decimal ConvertToUsd(BigInteger amount, int decimals, BigInteger price)
|
||||
{
|
||||
var result = (amount * price) / ExpandDecimals(1, decimals);
|
||||
return (decimal)result;
|
||||
}
|
||||
|
||||
public static BigInteger ParseValue(decimal value, int decimals)
|
||||
{
|
||||
return Web3.Convert.ToWei(value, decimals);
|
||||
}
|
||||
|
||||
public static string GetPositionKey(string addressesAccount, string addressesMarket,
|
||||
string addressesCollateralToken, bool flagsIsLong)
|
||||
{
|
||||
return $"{addressesAccount}:{addressesMarket}:{addressesCollateralToken}:{flagsIsLong}";
|
||||
}
|
||||
|
||||
public static BigInteger GetEntryPrice(BigInteger positionSizeInUsd, BigInteger positionSizeInTokens,
|
||||
int decimals)
|
||||
{
|
||||
if (positionSizeInTokens <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return BigInteger.Multiply(positionSizeInUsd, ExpandDecimals(1, decimals)) / positionSizeInTokens;
|
||||
}
|
||||
}
|
||||
107
src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs
Normal file
107
src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Numerics;
|
||||
using Managing.ABI.GmxV2.SyntheticsReader.ContractDefinition;
|
||||
using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
using Nethereum.Web3;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
internal static class GmxV2Mappers
|
||||
{
|
||||
public static Trade Map(GmxV2Position position, Enums.Ticker ticker)
|
||||
{
|
||||
var entryPrice = GmxV2Helpers.GetEntryPrice(position.SizeInUsd, position.SizeInTokens,
|
||||
position.TokenData.Decimals);
|
||||
var parsedEntryPrice = GmxHelpers.FormatAmount(entryPrice, Constants.GMX.Config.Decimals.USD - 2, 4);
|
||||
|
||||
var collateralLeverage = CalculateCollateralAndLeverage(position.SizeInUsd, position.CollateralAmount);
|
||||
var trade = new Trade(
|
||||
DateHelpers.GetFromUnixTimestamp((int)position.IncreasedAtTime),
|
||||
position.IsLong ? Enums.TradeDirection.Long : Enums.TradeDirection.Short,
|
||||
Enums.TradeStatus.Filled,
|
||||
Enums.TradeType.Limit,
|
||||
ticker,
|
||||
collateralLeverage.collateral / parsedEntryPrice,
|
||||
parsedEntryPrice,
|
||||
collateralLeverage.leverage,
|
||||
position.Key,
|
||||
""
|
||||
);
|
||||
|
||||
return trade;
|
||||
}
|
||||
|
||||
public static List<Trade> Map(List<GmxV2Order> orders)
|
||||
{
|
||||
var trades = new List<Trade>();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var shortToken = TokenV2Service.GetTokenByAddress(order.InitialCollateralTokenAddress);
|
||||
var ticker = GmxV2Helpers.GetTicker(order.MarketAddress);
|
||||
var indexToken = TokenV2Service.GetByTicker(ticker);
|
||||
var sizeDelta = GmxHelpers.FormatAmount(order.SizeDeltaUsd, Constants.GMX.Config.Decimals.USD, 4);
|
||||
var triggerPrice = GmxV2Helpers.ParseContractPrice(order.ContractTriggerPrice, indexToken.Decimals, true);
|
||||
var quantity = sizeDelta / triggerPrice;
|
||||
var initialCollateral =
|
||||
GmxV2Helpers.ParseContractPrice(order.InitialCollateralDeltaAmount, shortToken.Decimals);
|
||||
var leverage = sizeDelta / initialCollateral;
|
||||
|
||||
var trade = new Trade(
|
||||
order.Date,
|
||||
order.IsLong ? Enums.TradeDirection.Long : Enums.TradeDirection.Short,
|
||||
Enums.TradeStatus.PendingOpen,
|
||||
GmxV2Helpers.GetTradeType(order.OrderType),
|
||||
GmxV2Helpers.GetTicker(order.MarketAddress),
|
||||
Convert.ToDecimal(quantity),
|
||||
Convert.ToDecimal(triggerPrice),
|
||||
leverage,
|
||||
"",
|
||||
""
|
||||
);
|
||||
|
||||
trades.Add(trade);
|
||||
}
|
||||
|
||||
return trades;
|
||||
}
|
||||
//
|
||||
// public static GmxV2Position MapPosition(GmxV2Service.PositionInfoDTO positionInfo)
|
||||
// {
|
||||
// return new GmxV2Position
|
||||
// {
|
||||
// PositionKey = GetPositionKeyV2(
|
||||
// positionInfo.Position.Account,
|
||||
// positionInfo.Position.Market,
|
||||
// positionInfo.Position.CollateralToken,
|
||||
// positionInfo.Position.IsLong
|
||||
// ),
|
||||
// SizeInUsd = positionInfo.Position.SizeInUsd,
|
||||
// CollateralAmount = positionInfo.Position.CollateralAmount,
|
||||
// IncreasedAtTime = positionInfo.Position.IncreasedAtTime,
|
||||
// IsLong = positionInfo.Position.IsLong,
|
||||
// Leverage = CalculateLeverage(positionInfo.Position.SizeInUsd, positionInfo.Position.CollateralAmount)
|
||||
// };
|
||||
// }
|
||||
|
||||
private static (decimal collateral, decimal leverage) CalculateCollateralAndLeverage(BigInteger sizeInUsd,
|
||||
BigInteger collateralAmount)
|
||||
{
|
||||
if (collateralAmount == 0) return (0m, 0m);
|
||||
var size = Web3.Convert.FromWei(sizeInUsd, 30);
|
||||
var collateral = Web3.Convert.FromWei(collateralAmount, 6); // USDC decimals
|
||||
return (collateral, Math.Round(size / collateral));
|
||||
}
|
||||
|
||||
public static List<MarketPrices> Map(List<ABI.GmxV2.Reader.ContractDefinition.MarketPrices> marketPrices)
|
||||
{
|
||||
return marketPrices.Select(mp => new MarketPrices
|
||||
{
|
||||
IndexTokenPrice = mp.IndexTokenPrice,
|
||||
LongTokenPrice = mp.LongTokenPrice,
|
||||
ShortTokenPrice = mp.ShortTokenPrice
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
using System.Net.Http.Json;
|
||||
using Managing.Common;
|
||||
using Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
public class OracleKeeperService()
|
||||
{
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
private readonly string _baseUrl = Constants.GMX.Config.OracleKeeperUrl;
|
||||
|
||||
public async Task<List<GmxTicker>> GetTickers()
|
||||
{
|
||||
var response = await _httpClient.GetFromJsonAsync<List<GmxTicker>>(_baseUrl + "/prices/tickers");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Common;
|
||||
using Nethereum.Util;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
|
||||
public static class TokenV2Service
|
||||
{
|
||||
@@ -11,7 +12,8 @@ public static class TokenV2Service
|
||||
Name = "Ethereum",
|
||||
Symbol = "ETH",
|
||||
Decimals = 18,
|
||||
Address = Nethereum.Util.AddressUtil.ZERO_ADDRESS,
|
||||
PriceDecimals = 12,
|
||||
Address = AddressUtil.ZERO_ADDRESS,
|
||||
IsNative = true,
|
||||
IsShortable = true,
|
||||
ImageUrl = "https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880",
|
||||
@@ -23,6 +25,7 @@ public static class TokenV2Service
|
||||
Name = "Wrapped Ethereum",
|
||||
Symbol = "WETH",
|
||||
Decimals = 18,
|
||||
PriceDecimals = 12,
|
||||
Address = Constants.GMX.TokenAddress.WETH,
|
||||
IsWrapped = true,
|
||||
BaseSymbol = "ETH",
|
||||
@@ -35,6 +38,7 @@ public static class TokenV2Service
|
||||
Name = "Wrapped Stacked Ethereum",
|
||||
Symbol = "WSTETH",
|
||||
Decimals = 18,
|
||||
PriceDecimals = 12,
|
||||
Address = Constants.GMX.TokenAddress.WSTETH,
|
||||
IsWrapped = true,
|
||||
BaseSymbol = "wstETH",
|
||||
@@ -47,6 +51,7 @@ public static class TokenV2Service
|
||||
Symbol = "BTC",
|
||||
AssetSymbol = "WBTC",
|
||||
Decimals = 8,
|
||||
PriceDecimals = 22,
|
||||
Address = Constants.GMX.TokenAddress.WBTC,
|
||||
IsShortable = true,
|
||||
ImageUrl = "https://assets.coingecko.com/coins/images/26115/thumb/btcb.png?1655921693",
|
||||
@@ -423,6 +428,11 @@ public static class TokenV2Service
|
||||
|
||||
return $"{longToken}-{shortToken}";
|
||||
}
|
||||
|
||||
public static GmxToken GetByTicker(Enums.Ticker ticker)
|
||||
{
|
||||
return TOKENS.FirstOrDefault(t => t.Symbol == ticker.ToString()) ?? throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public class GmxToken
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user