equation-sdk
Version:
🛠An SDK for building applications on top of Equation.
183 lines (170 loc) • 8.31 kB
text/typescript
/**
* @module modules/markets
* @description Represents a PositionRouter.
*/
import { catchFn, computePriceX96, div, plus, getPositionRouterContract, formatMarket, priceRangesByTolerance, isPositive, isEqualTo, isGreaterThanOrEqual, multipliedBy, toDecimalPlaces, isLessThan } from '../utils'
import { parseUnits } from 'ethers/lib/utils';
import { Contract, Wallet } from 'ethers';
import { MaxUint256 } from '@ethersproject/constants';
import {
CONFIG,
Side,
DEFAULT_PRECISION,
DEFAULT_QUOTE_PRECISION,
DEFAULT_SETTING_SLIPPAGE,
DEFAULT_QUOTE_AMOUNT
} from '../config';
import { PriceUtil } from '../entities/PriceUtil';
import { UtilHelper } from '../entities/UtilHelper';
import { PositionUtil } from '../entities/PositionUtil';
import { Markets } from "./Markets";
import Approval from "./Approval";
import invariant from 'tiny-invariant';
export default class PositionRouter {
provider: Wallet;
positionRouterContract: Contract;
positionMinExecutionFee: string;
slippage: string;
approval: Approval;
account: string;
constructor(provider:Wallet, slippage = DEFAULT_SETTING_SLIPPAGE) {
this.provider = provider;
this.slippage = slippage;
this.positionRouterContract = getPositionRouterContract(provider);
this.positionMinExecutionFee = '0';
this.approval = new Approval(provider)
this.account = '';
this.executionFee();
this.getAccount();
}
async executionFee() {
const dataConfigInfo = await Markets.fetchGasConfig();
this.positionMinExecutionFee = dataConfigInfo.positionExecutionFee;
}
async getAccount() {
this.account = await this.provider.getAddress();
}
async approvalPosition(){
const allowanceAmount = await this.approval.fetchAllowance(CONFIG.Router)
if(!isGreaterThanOrEqual(allowanceAmount, DEFAULT_QUOTE_AMOUNT)){
await this.approval.approvalToken(CONFIG.Router)
}
const isPluginApproved = await this.approval.isPluginApproved(CONFIG.PositionRouter)
if(!isPluginApproved){
await this.approval.approvalPlugin(CONFIG.PositionRouter)
}
}
/**
* Create open or increase the size of existing position request
*
* @param market - The market in which to increase position
* @param side - The side of the position (Long or Short)
* @param marginDelta - The increase in position margin
* @param sizeDelta - TThe increase in position size
* @returns A Promise that resolves to the result of creating the increased position.
*/
async createIncreasePosition(
market: string,
side: Side,
marginDelta: string,
sizeDelta: string,
) {
const allowanceAmount = await this.approval.fetchAllowance(CONFIG.Router)
if(isLessThan(allowanceAmount, marginDelta)){
invariant(!isLessThan(allowanceAmount, marginDelta), 'Please approvalPosition the contract to spend tokens');
return;
}
const isPluginApproved = await this.approval.isPluginApproved(CONFIG.PositionRouter)
if(!isPluginApproved){
invariant(isPluginApproved, 'Please approvalPosition the contract to spend tokens');
return;
}
const marketAddresses = [market];
const markets = await Markets.fetchMarketList();
const tokensPrice = await Markets.fetchMarketTokensPrice(marketAddresses);
const tokensMultiPrice = await Markets.fetchMarketMultiTokens(marketAddresses);
const marketsItem = markets?.find((marketsItem: any) => marketsItem.address === market);
const tokenPriceItem = tokensPrice?.find((tokenItem: any) => tokenItem.address === market);
const tokenMultiPriceItem = tokensMultiPrice?.find((tokenItem: any) => tokenItem.address === market);
const marketInfo = formatMarket(marketsItem, tokenPriceItem, tokenMultiPriceItem);
const entryPrice = PriceUtil.calculateMarketPrice(
sizeDelta,
marketInfo.globalLiquidityPosition,
marketInfo.priceState,
side,
marketInfo.indexPriceX96,
DEFAULT_PRECISION
);
const { minPrice, maxPrice } = priceRangesByTolerance(entryPrice, this.slippage);
const acceptablePrice = side === Side.LONG ? maxPrice : minPrice;
const acceptablePriceX96 = computePriceX96(
acceptablePrice,
DEFAULT_PRECISION,
DEFAULT_QUOTE_PRECISION
);
try {
const res = await this.positionRouterContract.createIncreasePosition(market, side, parseUnits(toDecimalPlaces(marginDelta,DEFAULT_QUOTE_PRECISION), DEFAULT_QUOTE_PRECISION), parseUnits(toDecimalPlaces(sizeDelta,DEFAULT_PRECISION), DEFAULT_PRECISION), acceptablePriceX96, { value: this.positionMinExecutionFee })
return res.hash;
} catch (error) {
throw (`Failed to create increase position request. ${error}`);
}
}
/**
* Create decrease position request
*
* @param market - The market in which to decrease position
* @param side - The side of the position (Long or Short)
* @param sizeDelta - The decrease in position size
* @returns A Promise that resolves to the result of creating the decreased position.
*/
async createDecreasePosition(
market: string,
side: Side,
sizeDelta: string,
) {
const marketAddresses = [market];
const markets = await Markets.fetchMarketList();
const positions = await Markets.fetchPositions({"account": this.account, "market": market});
const tokensPrice = await Markets.fetchMarketTokensPrice(marketAddresses);
const tokensMultiPrice = await Markets.fetchMarketMultiTokens(marketAddresses);
const marketsItem = markets?.find((marketsItem: any) => marketsItem.address === market);
const tokenPriceItem = tokensPrice?.find((tokenItem: any) => tokenItem.address === market);
const tokenMultiPriceItem = tokensMultiPrice?.find((tokenItem: any) => tokenItem.address === market);
const positionInfo = positions?.find((positionItem: any) => positionItem.side === side);
const marketInfo = formatMarket(marketsItem, tokenPriceItem, tokenMultiPriceItem);
const flipSide = UtilHelper.flipSide(side);
const indexPriceX96 = flipSide === Side.LONG ? marketInfo.maxIndexPriceX96 : marketInfo.minIndexPriceX96
const tradePrice = catchFn(() => {
return PriceUtil.calculateMarketPrice(
sizeDelta,
marketInfo.globalLiquidityPosition,
marketInfo.priceState,
flipSide,
indexPriceX96,
DEFAULT_PRECISION
);
}, '');
const { minPrice, maxPrice } = priceRangesByTolerance(tradePrice, this.slippage);
const acceptablePrice = side === Side.LONG ? minPrice : maxPrice;
const acceptablePriceX96 = computePriceX96(
acceptablePrice,
DEFAULT_PRECISION,
DEFAULT_QUOTE_PRECISION
);
const lightenRatio = (!isPositive(tradePrice) || !isPositive(sizeDelta)) ? '0' : div(sizeDelta, positionInfo.size);
const UnrealizedPnL = PositionUtil.calculateUnrealizedPnL(
positionInfo.side,
positionInfo.size,
positionInfo.entryPrice,
tradePrice
);
const closeUnrealizedPnL = multipliedBy(lightenRatio, UnrealizedPnL);
const marginDelta = isEqualTo(lightenRatio, 1) ? '0' : plus(multipliedBy(lightenRatio, positionInfo.margin), closeUnrealizedPnL);
try {
const res = await this.positionRouterContract.createDecreasePosition(market, side, parseUnits(toDecimalPlaces(marginDelta,DEFAULT_QUOTE_PRECISION), DEFAULT_QUOTE_PRECISION), parseUnits(toDecimalPlaces(sizeDelta,DEFAULT_PRECISION), DEFAULT_PRECISION), acceptablePriceX96, this.account, { value: this.positionMinExecutionFee })
return res.hash;
} catch (error) {
throw (`Failed to create decrease position request. ${error}`);
}
}
}