@d8x/perpetuals-sdk 0.0.21 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,8 +29,17 @@ import {
29
29
  DEFAULT_CONFIG_TESTNET_NAME,
30
30
  DEFAULT_CONFIG_TESTNET,
31
31
  ONE_64x64,
32
+ PERP_STATE_STR,
33
+ PerpetualState,
32
34
  } from "./nodeSDKTypes";
33
- import { fromBytes4HexString, to4Chars, combineFlags, containsFlag } from "./utils";
35
+ import {
36
+ fromBytes4HexString,
37
+ to4Chars,
38
+ combineFlags,
39
+ containsFlag,
40
+ contractSymbolToSymbol,
41
+ symbol4BToLongSymbol,
42
+ } from "./utils";
34
43
  import {
35
44
  ABK64x64ToFloat,
36
45
  floatToABK64x64,
@@ -50,6 +59,8 @@ export default class PerpetualDataHandler {
50
59
  //this is initialized in the createProxyInstance function
51
60
  protected symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>;
52
61
  protected poolStaticInfos: Array<PoolStaticInfo>;
62
+ protected symbolList: Array<{ [key: string]: string }>;
63
+
53
64
  //map margin token of the form MATIC or ETH or USDC into
54
65
  //the address of the margin token
55
66
  protected symbolToTokenAddrMap: Map<string, string>;
@@ -83,6 +94,7 @@ export default class PerpetualDataHandler {
83
94
  this.proxyABI = require(config.proxyABILocation);
84
95
  this.lobFactoryABI = require(config.limitOrderBookFactoryABILocation);
85
96
  this.lobABI = require(config.limitOrderBookABILocation);
97
+ this.symbolList = require(config.symbolListLocation);
86
98
  }
87
99
 
88
100
  protected async initContractsAndData(signerOrProvider: ethers.Signer | ethers.providers.Provider) {
@@ -98,8 +110,7 @@ export default class PerpetualDataHandler {
98
110
  * @returns order book contract for the perpetual
99
111
  */
100
112
  public getOrderBookContract(symbol: string): ethers.Contract {
101
- let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
102
- let orderBookAddr = this.symbolToPerpStaticInfo.get(cleanSymbol)?.limitOrderBookAddr;
113
+ let orderBookAddr = this.symbolToPerpStaticInfo.get(symbol)?.limitOrderBookAddr;
103
114
  if (orderBookAddr == "" || orderBookAddr == undefined || this.signerOrProvider == null) {
104
115
  throw Error(`no limit order book found for ${symbol} or no signer`);
105
116
  }
@@ -132,10 +143,10 @@ export default class PerpetualDataHandler {
132
143
 
133
144
  for (let k = 0; k < perpetualIDs.length; k++) {
134
145
  let perp = await proxyContract.getPerpetual(perpetualIDs[k]);
135
- let base = fromBytes4HexString(perp.S2BaseCCY);
136
- let quote = fromBytes4HexString(perp.S2QuoteCCY);
137
- let base3 = fromBytes4HexString(perp.S3BaseCCY);
138
- let quote3 = fromBytes4HexString(perp.S3QuoteCCY);
146
+ let base = contractSymbolToSymbol(perp.S2BaseCCY, this.symbolList);
147
+ let quote = contractSymbolToSymbol(perp.S2QuoteCCY, this.symbolList);
148
+ let base3 = contractSymbolToSymbol(perp.S3BaseCCY, this.symbolList);
149
+ let quote3 = contractSymbolToSymbol(perp.S3QuoteCCY, this.symbolList);
139
150
  currentSymbols.push(base + "-" + quote);
140
151
  currentSymbolsS3.push(base3 + "-" + quote3);
141
152
  initRate.push(ABK64x64ToFloat(perp.fInitialMarginRate));
@@ -155,19 +166,17 @@ export default class PerpetualDataHandler {
155
166
  poolCCY = quote;
156
167
  ccy.push(CollaterlCCY.QUOTE);
157
168
  } else {
169
+ poolCCY = base3;
158
170
  ccy.push(CollaterlCCY.QUANTO);
159
171
  }
160
172
  }
161
173
  if (perpetualIDs.length == 0) {
162
174
  continue;
163
175
  }
164
- if (poolCCY == undefined) {
165
- throw Error("Pool only has quanto perps, unable to determine collateral currency");
166
- }
167
176
  let oracleFactoryAddr = await proxyContract.getOracleFactory();
168
177
  let info: PoolStaticInfo = {
169
178
  poolId: j + 1,
170
- poolMarginSymbol: poolCCY,
179
+ poolMarginSymbol: poolCCY!,
171
180
  poolMarginTokenAddr: poolMarginTokenAddr,
172
181
  shareTokenAddr: pool.shareTokenAddress,
173
182
  oracleFactoryAddr: oracleFactoryAddr,
@@ -188,7 +197,7 @@ export default class PerpetualDataHandler {
188
197
  });
189
198
  }
190
199
  // push margin token address into map
191
- this.symbolToTokenAddrMap.set(poolCCY, poolMarginTokenAddr);
200
+ this.symbolToTokenAddrMap.set(poolCCY!, poolMarginTokenAddr);
192
201
  }
193
202
  }
194
203
 
@@ -219,6 +228,21 @@ export default class PerpetualDataHandler {
219
228
  return PerpetualDataHandler.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
220
229
  }
221
230
 
231
+ /**
232
+ * Get the symbol in long format of the perpetual id
233
+ * @param perpId perpetual id
234
+ */
235
+ public getSymbolFromPerpId(perpId: number): string | undefined {
236
+ let symbol4Bytes = PerpetualDataHandler.perpetualIdToSymbol(perpId, this.symbolToPerpStaticInfo);
237
+ if (symbol4Bytes == undefined) {
238
+ return undefined;
239
+ }
240
+ }
241
+
242
+ public symbol4BToLongSymbol(sym: string): string {
243
+ return symbol4BToLongSymbol(sym, this.symbolList);
244
+ }
245
+
222
246
  protected static _getSymbolFromPoolId(poolId: number, staticInfos: PoolStaticInfo[]): string {
223
247
  let idx = poolId - 1;
224
248
  return staticInfos[idx].poolMarginSymbol;
@@ -230,10 +254,8 @@ export default class PerpetualDataHandler {
230
254
  if (symbols.length == 3) {
231
255
  symbol = symbols[2];
232
256
  }
233
- let cleanSymbol = to4Chars(symbol);
234
- cleanSymbol = cleanSymbol.replace(/\0/g, "");
235
257
  let j = 0;
236
- while (j < staticInfos.length && staticInfos[j].poolMarginSymbol != cleanSymbol) {
258
+ while (j < staticInfos.length && staticInfos[j].poolMarginSymbol != symbol) {
237
259
  j++;
238
260
  }
239
261
  if (j == staticInfos.length) {
@@ -262,8 +284,10 @@ export default class PerpetualDataHandler {
262
284
  symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
263
285
  _proxyContract: ethers.Contract
264
286
  ): Promise<MarginAccount> {
265
- let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
266
- let perpId = PerpetualDataHandler.symbolToPerpetualId(cleanSymbol, symbolToPerpStaticInfo);
287
+ let perpId = Number(symbol);
288
+ if (isNaN(perpId)) {
289
+ perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
290
+ }
267
291
  const idx_cash = 3;
268
292
  const idx_notional = 4;
269
293
  const idx_locked_in = 5;
@@ -283,7 +307,7 @@ export default class PerpetualDataHandler {
283
307
  entryPrice = 0;
284
308
  if (!isEmpty) {
285
309
  [S2Liq, S3Liq, tau, pnl, unpaidFundingCC] = PerpetualDataHandler._calculateLiquidationPrice(
286
- cleanSymbol,
310
+ symbol,
287
311
  traderState,
288
312
  symbolToPerpStaticInfo
289
313
  );
@@ -329,15 +353,40 @@ export default class PerpetualDataHandler {
329
353
  return ABK64x64ToFloat(ammState[6].mul(ONE_64x64.add(ammState[8])).div(ONE_64x64));
330
354
  }
331
355
 
356
+ protected static async _queryPerpetualState(
357
+ symbol: string,
358
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
359
+ _proxyContract: ethers.Contract
360
+ ): Promise<PerpetualState> {
361
+ let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
362
+ let ccy = symbol.split("-");
363
+ let ammState = await _proxyContract.getAMMState(perpId);
364
+ let markPrice = ABK64x64ToFloat(ammState[6].mul(ONE_64x64.add(ammState[8])).div(ONE_64x64));
365
+ let state = {
366
+ id: perpId,
367
+ state: PERP_STATE_STR[ammState[13]],
368
+ baseCurrency: ccy[0],
369
+ quoteCurrency: ccy[1],
370
+ indexPrice: ABK64x64ToFloat(ammState[6]),
371
+ collToQuoteIndexPrice: ABK64x64ToFloat(ammState[7]),
372
+ markPrice: markPrice,
373
+ midPrice: ABK64x64ToFloat(ammState[10]),
374
+ currentFundingRateBps: ABK64x64ToFloat(ammState[14]) * 1e4,
375
+ openInterestBC: ABK64x64ToFloat(ammState[11]),
376
+ maxPositionBC: ABK64x64ToFloat(ammState[12]),
377
+ };
378
+ return state;
379
+ }
380
+
332
381
  /**
333
382
  * Liquidation price
334
- * @param cleanSymbol symbol after calling symbolToBytes4Symbol
383
+ * @param symbol symbol of the form BTC-USD-MATIC
335
384
  * @param traderState BigInt array according to smart contract
336
385
  * @param symbolToPerpStaticInfo mapping symbol->PerpStaticInfo
337
386
  * @returns liquidation mark-price, corresponding collateral/quote conversion
338
387
  */
339
388
  protected static _calculateLiquidationPrice(
340
- cleanSymbol: string,
389
+ symbol: string,
341
390
  traderState: BigNumber[],
342
391
  symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
343
392
  ): [number, number, number, number, number] {
@@ -350,9 +399,9 @@ export default class PerpetualDataHandler {
350
399
  const idx_s2 = 10;
351
400
  let S2Liq: number;
352
401
  let S3Liq: number = ABK64x64ToFloat(traderState[idx_s3]);
353
- let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(cleanSymbol);
402
+ let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(symbol);
354
403
  if (perpInfo == undefined) {
355
- throw new Error(`no info for perpetual ${cleanSymbol}`);
404
+ throw new Error(`no info for perpetual ${symbol}`);
356
405
  }
357
406
  let tau = perpInfo.maintenanceMarginRate;
358
407
  let lockedInValueQC = ABK64x64ToFloat(traderState[idx_locked_in]);
@@ -382,7 +431,7 @@ export default class PerpetualDataHandler {
382
431
  * Finds the perpetual id for a symbol of the form
383
432
  * <base>-<quote>-<collateral>. The function first converts the
384
433
  * token names into bytes4 representation
385
- * @param symbol symbol (e.g., BTC-USD-MATIC)
434
+ * @param symbol symbol (e.g., BTC-USD-MATC)
386
435
  * @param symbolToPerpStaticInfo map that contains the bytes4-symbol to PerpetualStaticInfo
387
436
  * including id mapping
388
437
  * @returns perpetual id or it fails
@@ -391,14 +440,32 @@ export default class PerpetualDataHandler {
391
440
  symbol: string,
392
441
  symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
393
442
  ): number {
394
- let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
395
- let id = symbolToPerpStaticInfo.get(cleanSymbol)?.id;
443
+ let id = symbolToPerpStaticInfo.get(symbol)?.id;
396
444
  if (id == undefined) {
397
445
  throw Error(`No perpetual found for symbol ${symbol}`);
398
446
  }
399
447
  return id;
400
448
  }
401
449
 
450
+ /**
451
+ * Find the symbol ("ETH-USD-MATC") of the given perpetual id
452
+ * @param id perpetual id
453
+ * @param symbolToPerpStaticInfo map that contains the bytes4-symbol to PerpetualStaticInfo
454
+ * @returns symbol string or undefined
455
+ */
456
+ protected static perpetualIdToSymbol(
457
+ id: number,
458
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
459
+ ): string | undefined {
460
+ let symbol;
461
+ for (symbol of symbolToPerpStaticInfo.keys()) {
462
+ if (symbolToPerpStaticInfo.get(symbol)?.id == id) {
463
+ return symbol;
464
+ }
465
+ }
466
+ return undefined;
467
+ }
468
+
402
469
  protected static symbolToBytes4Symbol(symbol: string): string {
403
470
  //split by dashes BTC-USD-MATIC
404
471
  let symbols: string[] = symbol.split("-");
@@ -588,8 +655,7 @@ export default class PerpetualDataHandler {
588
655
  }
589
656
 
590
657
  protected static _getLotSize(symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>): number {
591
- let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
592
- let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(cleanSymbol);
658
+ let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(symbol);
593
659
  if (perpInfo == undefined) {
594
660
  throw new Error(`no info for perpetual ${symbol}`);
595
661
  }
@@ -0,0 +1,441 @@
1
+ import { BigNumber, utils } from "ethers";
2
+ import { ABK64x64ToFloat, mul64x64, div64x64 } from "./d8XMath";
3
+ import {
4
+ PerpetualState,
5
+ ONE_64x64,
6
+ ExchangeInfo,
7
+ Order,
8
+ SmartContractOrder,
9
+ MarginAccount,
10
+ OrderStruct,
11
+ TradeEvent,
12
+ } from "./nodeSDKTypes";
13
+ import { emitWarning } from "process";
14
+ import MarketData from "./marketData";
15
+
16
+ /**
17
+ * This class handles events and stores relevant variables
18
+ * as member variables. The events change the state of the member variables:
19
+ * mktData : MarketData relevant market data with current state (e.g. index price)
20
+ * ordersInPerpetual: Map<number, OrderStruct> all open orders for the given trader
21
+ * positionInPerpetual: Map<number, MarginAccount> all open positions for the given trader
22
+ *
23
+ * TODO:
24
+ * - update functions for midprice & index & collateral prices without event
25
+ * - testing
26
+ *
27
+ * Get data:
28
+ * - getPerpetualData(perp id (string) or symbol) : PerpetualState. This is a reference!
29
+ * - getExchangeInfo() : ExchangeInfo. This is a reference!
30
+ * - getCurrentPositionRisk(perp id (string) or symbol) : MarginAccount. This is a reference!
31
+ * - getOrdersInPerpetualMap : Map<number, OrderStruct>. This is a reference!
32
+ * - getpositionInPerpetualMap : Map<number, MarginAccount>. This is a reference!
33
+ *
34
+ * Construct with a trader address and a marketData object
35
+ * Initialize to gather all the relevant data.
36
+ * Send event variables to event handler "on<EventName>" - this updates members
37
+ * - [x] onUpdateMarkPrice : emitted on proxy; updates markprice and index price data
38
+ * - [x] onUpdateUpdateFundingRate : emitted on proxy; sets funding rate
39
+ * - [x] onExecutionFailed : emitted on order book; removes an open order
40
+ * - [x] onPerpetualLimitOrderCancelled : emitted on order book; removes an open order
41
+ * - [x] onPerpetualLimitOrderCreated : emitted on order book; adds an open order to the data
42
+ * - [x] async onUpdateMarginAccount : emitted on proxy; updates position data and open interest
43
+ * - [x] onTrade : emitted on proxy; returns TradeEvent to be displayed
44
+ */
45
+ export default class PerpetualEventHandler {
46
+ // market data class
47
+ private mktData: MarketData;
48
+ // trader for which the data is tracked
49
+ private traderAddr: string;
50
+
51
+ // perpetual id to trader data
52
+ private ordersInPerpetual: Map<number, OrderStruct>;
53
+ private positionInPerpetual: Map<number, MarginAccount>;
54
+
55
+ // keep current state of the system in exchangeInfo
56
+ // data is updated when calling "onEvent"-functions
57
+ private exchangeInfo: ExchangeInfo | undefined;
58
+
59
+ constructor(mktData: MarketData, traderAddr: string) {
60
+ this.mktData = mktData;
61
+ this.traderAddr = traderAddr;
62
+ this.ordersInPerpetual = new Map<number, OrderStruct>();
63
+ this.positionInPerpetual = new Map<number, MarginAccount>();
64
+ }
65
+
66
+ /**
67
+ * Call this async function to initialize the
68
+ * market data
69
+ */
70
+ public async initialize() {
71
+ this.exchangeInfo = await this.mktData.exchangeInfo();
72
+ // loop through all pools and perpetuals and get
73
+ // open positions and open orders
74
+ for (let k = 0; k < this.exchangeInfo.pools.length; k++) {
75
+ let poolState = this.exchangeInfo.pools[k];
76
+ let poolSymbol = poolState.poolSymbol;
77
+ for (let j = 0; j < poolState.perpetuals.length; j++) {
78
+ let perpState: PerpetualState = poolState.perpetuals[j];
79
+ let perpSymbol = perpState.baseCurrency + "-" + perpState.quoteCurrency + "-" + poolSymbol;
80
+ let orders = await this.mktData.openOrders(this.traderAddr, perpSymbol);
81
+ let perpId = perpState.id;
82
+ this.ordersInPerpetual.set(perpId, orders);
83
+ let position = await this.mktData.positionRisk(this.traderAddr, perpSymbol);
84
+ this.positionInPerpetual.set(perpId, position);
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Get the current exchange info
91
+ * @returns exchange info
92
+ */
93
+ public getExchangeInfo(): ExchangeInfo | undefined {
94
+ return this.exchangeInfo;
95
+ }
96
+
97
+ /**
98
+ * getOrdersInPerpetualMap
99
+ * @returns this.ordersInPerpetual
100
+ */
101
+ public getOrdersInPerpetualMap(): Map<number, OrderStruct> {
102
+ return this.ordersInPerpetual;
103
+ }
104
+
105
+ /**
106
+ * getpositionInPerpetualMap
107
+ * @returns this.positionInPerpetual
108
+ */
109
+ public getpositionInPerpetualMap(): Map<number, MarginAccount> {
110
+ return this.positionInPerpetual;
111
+ }
112
+
113
+ /**
114
+ * Get the data for a perpetual with a given index
115
+ * @param perpetualIdOrSymbol perpetual idx as string or symbol for which we want the data
116
+ * @returns perpetual data for this idx
117
+ */
118
+ public getPerpetualData(perpetualIdOrSymbol: string): PerpetualState | undefined {
119
+ let perpId = Number(perpetualIdOrSymbol);
120
+ if (isNaN(perpId)) {
121
+ perpId = this.mktData.getPerpIdFromSymbol(perpetualIdOrSymbol);
122
+ }
123
+ //uint24 perpetualId = uint24(_iPoolId) * 100_000 + iPerpetualIndex;
124
+ let poolIdx = Math.floor(perpId / 100_000);
125
+ let perpetuals = this.exchangeInfo?.pools[poolIdx].perpetuals;
126
+ if (perpetuals == undefined) {
127
+ emitWarning(`exchangeInfo not found, initialize perpetualEventHandler`);
128
+ return undefined;
129
+ }
130
+ // find perpetual
131
+ let k;
132
+ for (k = 0; k < perpetuals?.length && perpetuals[k].id != perpId; k++);
133
+ if (perpetuals[k].id != perpId) {
134
+ emitWarning(`getPerpetualData: perpetual id ${perpId} not found`);
135
+ return undefined;
136
+ }
137
+ return perpetuals[k];
138
+ }
139
+
140
+ /**
141
+ * Get the trader's current position risk (margin account data)
142
+ * @param perpetualIdOrSymbol perpetual id as string ('100003') or symbol ('BTC-USD-MATIC')
143
+ * @returns undefined if no position or margin account (='position risk')
144
+ */
145
+ public getCurrentPositionRisk(perpetualIdOrSymbol: string): MarginAccount | undefined {
146
+ let perpId = Number(perpetualIdOrSymbol);
147
+ if (isNaN(perpId)) {
148
+ perpId = this.mktData.getPerpIdFromSymbol(perpetualIdOrSymbol);
149
+ }
150
+ return this.positionInPerpetual.get(perpId);
151
+ }
152
+
153
+ /**
154
+ * Update the following prices:
155
+ * - index price
156
+ * - collateral price
157
+ * - mid-price
158
+ * @param perpetualIdOrSymbol perpetual id as string ('100003') or symbol ('BTC-USD-MATIC')
159
+ */
160
+ public async updatePrices(perpetualIdOrSymbol: string) {
161
+ let perpId = Number(perpetualIdOrSymbol);
162
+ let symbol = perpetualIdOrSymbol;
163
+ if (!isNaN(perpId)) {
164
+ let sym = this.mktData.getSymbolFromPerpId(perpId);
165
+ if (sym == undefined) {
166
+ throw new Error(`Symbol not found for perpetual ${perpId}`);
167
+ }
168
+ symbol = sym;
169
+ }
170
+ let perpState: PerpetualState = await this.mktData.getPerpetualState(symbol);
171
+ let perp = this.getPerpetualData(symbol);
172
+ if (perp == undefined) {
173
+ throw new Error(`Perpetual not found: ${symbol}`);
174
+ }
175
+ perp.state = perpState.state;
176
+ perp.indexPrice = perpState.indexPrice;
177
+ perp.collToQuoteIndexPrice = perpState.collToQuoteIndexPrice;
178
+ perp.markPrice = perpState.markPrice;
179
+ perp.midPrice = perpState.midPrice;
180
+ perp.currentFundingRateBps = perpState.currentFundingRateBps;
181
+ perp.openInterestBC = perpState.openInterestBC;
182
+ perp.maxPositionBC = perpState.maxPositionBC;
183
+ perp.indexPrice = perpState.indexPrice;
184
+ perp.collToQuoteIndexPrice = perpState.collToQuoteIndexPrice;
185
+ }
186
+
187
+ /**
188
+ * Handle the event UpdateMarkPrice and update relevant
189
+ * data
190
+ * @param perpetualId perpetual Id
191
+ * @param fMarkPricePremium premium rate in ABDK format
192
+ * @param fSpotIndexPrice spot index price in ABDK format
193
+ * @returns void
194
+ */
195
+ public onUpdateMarkPrice(perpetualId: number, fMarkPricePremium: BigNumber, fSpotIndexPrice: BigNumber): void {
196
+ let [newMarkPrice, newIndexPrice] = PerpetualEventHandler.ConvertUpdateMarkPrice(
197
+ fMarkPricePremium,
198
+ fSpotIndexPrice
199
+ );
200
+ let perpetual = this.getPerpetualData(perpetualId.toString());
201
+ if (perpetual == undefined) {
202
+ return;
203
+ }
204
+ perpetual.markPrice = newMarkPrice;
205
+ perpetual.indexPrice = newIndexPrice;
206
+ }
207
+
208
+ /**
209
+ * Handle the event UpdateFundingRate and update relevant
210
+ * data
211
+ * UpdateFundingRate(uint24 indexed perpetualId, int128 fFundingRate)
212
+ * @param fFundingRate funding rate in ABDK format
213
+ */
214
+ public onUpdateUpdateFundingRate(perpetualId: number, fFundingRate: BigNumber): void {
215
+ let newRate = ABK64x64ToFloat(fFundingRate);
216
+ let perpetual = this.getPerpetualData(perpetualId.toString());
217
+ if (perpetual == undefined) {
218
+ return;
219
+ }
220
+ perpetual.currentFundingRateBps = newRate * 1e4;
221
+ }
222
+
223
+ /**
224
+ * event ExecutionFailed(
225
+ uint24 indexed perpetualId,
226
+ address indexed trader,
227
+ bytes32 digest,
228
+ string reason
229
+ );
230
+ * @param perpetualId id of the perpetual
231
+ * @param trader address of the trader
232
+ * @param digest digest of the order/cancel order
233
+ * @param reason reason why the execution failed
234
+ */
235
+ public onExecutionFailed(perpetualId: number, trader: string, digest: string, reason: string) {
236
+ if (trader != this.traderAddr) {
237
+ emitWarning(`onExecutionFailed: trader ${trader} not relevant`);
238
+ return;
239
+ }
240
+ // remove order from open orders
241
+ let orderStructs:
242
+ | {
243
+ orders: Order[];
244
+ orderIds: string[];
245
+ }
246
+ | undefined = this.ordersInPerpetual.get(perpetualId);
247
+ if (orderStructs == undefined) {
248
+ emitWarning(`onExecutionFailed: no order found for perpetual ${perpetualId}`);
249
+ return;
250
+ }
251
+ if (reason == "cancel delay required") {
252
+ // canceling failed. We don't remove the order
253
+ return;
254
+ }
255
+ PerpetualEventHandler.deleteOrder(orderStructs, digest);
256
+ }
257
+
258
+ /**
259
+ * Event emitted by perpetual proxy
260
+ * event PerpetualLimitOrderCancelled(bytes32 indexed orderHash);
261
+ * @param orderId string order id/digest
262
+ */
263
+ public onPerpetualLimitOrderCancelled(orderId: string) {
264
+ // remove order from open orders
265
+ let perpId: number | undefined = PerpetualEventHandler.findOrderForId(orderId, this.ordersInPerpetual);
266
+ if (perpId == undefined) {
267
+ emitWarning(`onPerpetualLimitOrderCancelled: no order found with id ${orderId}`);
268
+ return;
269
+ }
270
+ let orderStruct: OrderStruct | undefined = this.ordersInPerpetual.get(perpId);
271
+ PerpetualEventHandler.deleteOrder(orderStruct!, orderId);
272
+ }
273
+
274
+ /**
275
+ * event PerpetualLimitOrderCreated(
276
+ * uint24 indexed perpetualId,
277
+ * address indexed trader,
278
+ * address referrerAddr,
279
+ * address brokerAddr,
280
+ * Order order,
281
+ * bytes32 digest
282
+ *)
283
+ * @param perpetualId id of the perpetual
284
+ * @param trader address of the trader
285
+ * @param referrerAddr address of the referrer
286
+ * @param brokerAddr address of the broker
287
+ * @param Order order struct
288
+ * @param digest order id
289
+ */
290
+ public onPerpetualLimitOrderCreated(
291
+ perpetualId: number,
292
+ trader: string,
293
+ referrerAddr: string,
294
+ brokerAddr: string,
295
+ Order: SmartContractOrder,
296
+ digest: string
297
+ ): void {
298
+ if (trader != this.traderAddr) {
299
+ emitWarning(`onPerpetualLimitOrderCreated: trader ${trader} not relevant`);
300
+ return;
301
+ }
302
+ let order: Order = this.mktData.smartContractOrderToOrder(Order);
303
+ let orderStruct: OrderStruct | undefined = this.ordersInPerpetual.get(perpetualId);
304
+ if (orderStruct == undefined) {
305
+ // no order for this perpetual so far
306
+ this.ordersInPerpetual.set(perpetualId, { orders: [order], orderIds: [digest] });
307
+ } else {
308
+ orderStruct.orderIds.push(digest);
309
+ orderStruct.orders.push(order);
310
+ }
311
+ }
312
+
313
+ /**
314
+ * This function is async -> queries the margin account
315
+ * @param perpetualId id of the perpetual
316
+ * @param trader trader address
317
+ * @param positionId position id
318
+ * @param fPositionBC position size in base currency
319
+ * @param fCashCC margin collateral in margin account
320
+ * @param fLockedInValueQC pos*average opening price
321
+ * @param fFundingPaymentCC funding payment made
322
+ * @param fOpenInterestBC open interest
323
+ */
324
+ public async onUpdateMarginAccount(
325
+ perpetualId: number,
326
+ trader: string,
327
+ positionId: string,
328
+ fPositionBC: BigNumber,
329
+ fCashCC: BigNumber,
330
+ fLockedInValueQC: BigNumber,
331
+ fFundingPaymentCC: BigNumber,
332
+ fOpenInterestBC: BigNumber
333
+ ): Promise<void> {
334
+ let perpetual = this.getPerpetualData(perpetualId.toString());
335
+ if (perpetual == undefined) {
336
+ emitWarning(`onUpdateMarginAccount: perpetual ${perpetualId} not found`);
337
+ return;
338
+ }
339
+ perpetual.openInterestBC = ABK64x64ToFloat(fOpenInterestBC);
340
+ if (trader != this.traderAddr) {
341
+ return;
342
+ }
343
+ let perpetualIdStr = perpetualId.toString();
344
+ let margin = await this.mktData.positionRisk(this.traderAddr, perpetualIdStr);
345
+ this.positionInPerpetual.set(perpetualId, margin);
346
+ }
347
+
348
+ /**
349
+ *
350
+ * @param perpetualId perpetual id
351
+ * @param trader trader address
352
+ * @param positionId position id
353
+ * @param order order struct
354
+ * @param orderDigest order id
355
+ * @param newPositionSizeBC new pos size in base currency ABDK
356
+ * @param price price in ABDK format
357
+ * @returns trade event data in regular number format
358
+ */
359
+ public onTrade(
360
+ perpetualId: number,
361
+ trader: string,
362
+ positionId: string,
363
+ order: SmartContractOrder,
364
+ orderDigest: string,
365
+ newPositionSizeBC: BigNumber,
366
+ price: BigNumber
367
+ ): TradeEvent {
368
+ // remove order digest from open orders
369
+ let orderStructs = this.ordersInPerpetual.get(perpetualId);
370
+ if (orderStructs == undefined) {
371
+ emitWarning(`onTrade: executed order not found ${orderDigest}`);
372
+ } else {
373
+ PerpetualEventHandler.deleteOrder(orderStructs, orderDigest);
374
+ }
375
+ // return transformed trade info
376
+ return {
377
+ perpetualId: perpetualId,
378
+ positionId: positionId,
379
+ orderId: orderDigest,
380
+ newPositionSizeBC: ABK64x64ToFloat(newPositionSizeBC),
381
+ executionPrice: ABK64x64ToFloat(newPositionSizeBC),
382
+ };
383
+ }
384
+
385
+ /**
386
+ * static function to find the number of the OrderStruct with given orderId
387
+ * @param orderId id/digest of order
388
+ * @param orderMap mapping for perpetualId->OrderStruct
389
+ * @returns id of perpetual that contains order with id = orderId or undefined
390
+ */
391
+ private static findOrderForId(orderId: string, orderMap: Map<number, OrderStruct>): number | undefined {
392
+ /*orderMapMap<number, {
393
+ orders: Order[];
394
+ orderIds: string[];*/
395
+ for (const perpId of orderMap.keys()) {
396
+ let orderStructs = orderMap.get(perpId);
397
+ if (orderStructs?.orderIds.includes(orderId)) {
398
+ return perpId;
399
+ }
400
+ }
401
+ return undefined;
402
+ }
403
+
404
+ /**
405
+ * Delete the order with given id from the class member data
406
+ * @param orderStructs array of order struct as stored for the trader and a given perpetual
407
+ * @param orderId digest/order id
408
+ * @returns void
409
+ */
410
+ private static deleteOrder(orderStructs: OrderStruct, orderId: string): void {
411
+ // find order
412
+ let k;
413
+ for (k = 0; k < orderStructs.orderIds.length && orderStructs.orderIds[k] != orderId; k++);
414
+ if (orderStructs.orderIds[k] != orderId) {
415
+ emitWarning(`deleteOrder: no order found with digest ${orderId}`);
416
+ return;
417
+ }
418
+ // delete element k on reference of orders
419
+ orderStructs.orders[k] = orderStructs.orders[orderStructs.orders.length - 1];
420
+ orderStructs.orders.pop();
421
+ orderStructs.orderIds[k] = orderStructs.orderIds[orderStructs.orderIds.length - 1];
422
+ orderStructs.orderIds.pop();
423
+ }
424
+
425
+ /**
426
+ * UpdateMarkPrice(
427
+ * uint24 indexed perpetualId,
428
+ * int128 fMarkPricePremium,
429
+ * int128 fSpotIndexPrice
430
+ * )
431
+ * @param fMarkPricePremium premium rate in ABDK format
432
+ * @param fSpotIndexPrice spot index price in ABDK format
433
+ * @returns mark price and spot index in float
434
+ */
435
+ private static ConvertUpdateMarkPrice(fMarkPricePremium: BigNumber, fSpotIndexPrice: BigNumber): [number, number] {
436
+ let fMarkPrice = mul64x64(fSpotIndexPrice, ONE_64x64.add(fMarkPricePremium));
437
+ let markPrice = ABK64x64ToFloat(fMarkPrice);
438
+ let indexPrice = ABK64x64ToFloat(fSpotIndexPrice);
439
+ return [markPrice, indexPrice];
440
+ }
441
+ }