@d8x/perpetuals-sdk 0.6.3 → 0.6.5

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.
Files changed (48) hide show
  1. package/dist/cjs/version.d.ts +1 -1
  2. package/dist/cjs/version.js +1 -1
  3. package/dist/esm/version.d.ts +1 -1
  4. package/dist/esm/version.js +1 -1
  5. package/package.json +5 -4
  6. package/src/abi/ERC20.json +288 -0
  7. package/src/abi/IPerpetualManager.json +5888 -0
  8. package/src/abi/LimitOrderBook.json +1062 -0
  9. package/src/abi/LimitOrderBookFactory.json +161 -0
  10. package/src/abi/MockTokenSwap.json +186 -0
  11. package/src/abi/ShareToken.json +428 -0
  12. package/src/accountTrade.ts +428 -0
  13. package/src/brokerTool.ts +555 -0
  14. package/src/config/defaultConfig.json +62 -0
  15. package/src/config/mockSwap.json +6 -0
  16. package/src/config/priceFeedConfig.json +104 -0
  17. package/src/config/symbolList.json +13 -0
  18. package/src/contracts/ERC20.ts +444 -0
  19. package/src/contracts/IPerpetualManager.ts +7227 -0
  20. package/src/contracts/LimitOrderBook.ts +1251 -0
  21. package/src/contracts/LimitOrderBookFactory.ts +348 -0
  22. package/src/contracts/MockTokenSwap.ts +373 -0
  23. package/src/contracts/ShareToken.ts +695 -0
  24. package/src/contracts/common.ts +44 -0
  25. package/src/contracts/factories/ERC20__factory.ts +306 -0
  26. package/src/contracts/factories/IPerpetualManager__factory.ts +5912 -0
  27. package/src/contracts/factories/LimitOrderBookFactory__factory.ts +189 -0
  28. package/src/contracts/factories/LimitOrderBook__factory.ts +1086 -0
  29. package/src/contracts/factories/MockTokenSwap__factory.ts +207 -0
  30. package/src/contracts/factories/ShareToken__factory.ts +449 -0
  31. package/src/contracts/factories/index.ts +9 -0
  32. package/src/contracts/index.ts +16 -0
  33. package/src/d8XMath.ts +376 -0
  34. package/src/index.ts +29 -0
  35. package/src/liquidatorTool.ts +270 -0
  36. package/src/liquidityProviderTool.ts +148 -0
  37. package/src/marketData.ts +1310 -0
  38. package/src/nodeSDKTypes.ts +332 -0
  39. package/src/orderReferrerTool.ts +516 -0
  40. package/src/perpetualDataHandler.ts +1161 -0
  41. package/src/perpetualEventHandler.ts +455 -0
  42. package/src/priceFeeds.ts +382 -0
  43. package/src/traderDigests.ts +86 -0
  44. package/src/traderInterface.ts +172 -0
  45. package/src/triangulator.ts +105 -0
  46. package/src/utils.ts +134 -0
  47. package/src/version.ts +1 -0
  48. package/src/writeAccessHandler.ts +139 -0
@@ -0,0 +1,1161 @@
1
+ import { FormatTypes } from "@ethersproject/abi";
2
+ import { Signer } from "@ethersproject/abstract-signer";
3
+ import { BigNumber } from "@ethersproject/bignumber";
4
+ import { AddressZero } from "@ethersproject/constants";
5
+ import { CallOverrides, Contract, ContractInterface } from "@ethersproject/contracts";
6
+ import { Network, Provider } from "@ethersproject/providers";
7
+ import {
8
+ IPerpetualManager,
9
+ IPerpetualManager__factory,
10
+ LimitOrderBook,
11
+ LimitOrderBookFactory,
12
+ LimitOrderBookFactory__factory,
13
+ LimitOrderBook__factory,
14
+ } from "./contracts";
15
+ import { IPerpetualOrder } from "./contracts/IPerpetualManager";
16
+ import { IClientOrder } from "./contracts/LimitOrderBook";
17
+ import {
18
+ ABDK29ToFloat,
19
+ ABK64x64ToFloat,
20
+ calculateLiquidationPriceCollateralBase,
21
+ calculateLiquidationPriceCollateralQuanto,
22
+ calculateLiquidationPriceCollateralQuote,
23
+ div64x64,
24
+ floatToABK64x64,
25
+ } from "./d8XMath";
26
+ import {
27
+ BUY_SIDE,
28
+ ClientOrder,
29
+ CLOSED_SIDE,
30
+ COLLATERAL_CURRENCY_BASE,
31
+ COLLATERAL_CURRENCY_QUOTE,
32
+ CollaterlCCY,
33
+ DEFAULT_CONFIG,
34
+ loadABIs,
35
+ MarginAccount,
36
+ MASK_CLOSE_ONLY,
37
+ MASK_KEEP_POS_LEVERAGE,
38
+ MASK_LIMIT_ORDER,
39
+ MASK_MARKET_ORDER,
40
+ MASK_STOP_ORDER,
41
+ MAX_64x64,
42
+ NodeSDKConfig,
43
+ Order,
44
+ ORDER_MAX_DURATION_SEC,
45
+ ORDER_TYPE_LIMIT,
46
+ ORDER_TYPE_MARKET,
47
+ ORDER_TYPE_STOP_LIMIT,
48
+ ORDER_TYPE_STOP_MARKET,
49
+ PerpetualState,
50
+ PerpetualStaticInfo,
51
+ PERP_STATE_STR,
52
+ PoolStaticInfo,
53
+ PriceFeedSubmission,
54
+ SELL_SIDE,
55
+ SmartContractOrder,
56
+ SYMBOL_LIST,
57
+ ZERO_ADDRESS,
58
+ ZERO_ORDER_ID,
59
+ } from "./nodeSDKTypes";
60
+ import PriceFeeds from "./priceFeeds";
61
+ import { combineFlags, containsFlag, contractSymbolToSymbol, symbol4BToLongSymbol, to4Chars } from "./utils";
62
+
63
+ /**
64
+ * Parent class for MarketData and WriteAccessHandler that handles
65
+ * common data and chain operations.
66
+ */
67
+ export default class PerpetualDataHandler {
68
+ PRICE_UPDATE_FEE_GWEI = 1;
69
+ //map symbol of the form ETH-USD-MATIC into perpetual ID and other static info
70
+ //this is initialized in the createProxyInstance function
71
+ protected symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>; // maps symbol of the form BTC-USD-MATIC to static info
72
+ protected perpetualIdToSymbol: Map<number, string>; // maps unique perpetual id to symbol of the form BTC-USD-MATIC
73
+ protected poolStaticInfos: Array<PoolStaticInfo>;
74
+ protected symbolList: Map<string, string>; //mapping 4-digit symbol <-> long format
75
+
76
+ //map margin token of the form MATIC or ETH or USDC into
77
+ //the address of the margin token
78
+ protected symbolToTokenAddrMap: Map<string, string>;
79
+ protected chainId: number;
80
+ protected proxyContract: IPerpetualManager | null = null;
81
+ protected proxyABI: ContractInterface;
82
+ protected proxyAddr: string;
83
+ // limit order book
84
+ protected lobFactoryContract: LimitOrderBookFactory | null = null;
85
+ protected lobFactoryABI: ContractInterface;
86
+ protected lobFactoryAddr: string | undefined;
87
+ protected lobABI: ContractInterface;
88
+ protected shareTokenABI: ContractInterface;
89
+ protected nodeURL: string;
90
+ protected provider: Provider | null = null;
91
+
92
+ private signerOrProvider: Signer | Provider | null = null;
93
+ protected priceFeedGetter: PriceFeeds;
94
+
95
+ // pools are numbered consecutively starting at 1
96
+ // nestedPerpetualIDs contains an array for each pool
97
+ // each pool-array contains perpetual ids
98
+ protected nestedPerpetualIDs: number[][];
99
+
100
+ public constructor(config: NodeSDKConfig) {
101
+ this.symbolToPerpStaticInfo = new Map<string, PerpetualStaticInfo>();
102
+ this.poolStaticInfos = new Array<PoolStaticInfo>();
103
+ this.symbolToTokenAddrMap = new Map<string, string>();
104
+ this.perpetualIdToSymbol = new Map<number, string>();
105
+ this.nestedPerpetualIDs = new Array<Array<number>>();
106
+ this.chainId = config.chainId;
107
+ this.proxyAddr = config.proxyAddr;
108
+ this.nodeURL = config.nodeURL;
109
+ this.proxyABI = config.proxyABI!;
110
+ this.lobFactoryABI = config.lobFactoryABI!;
111
+ this.lobABI = config.lobABI!;
112
+ this.shareTokenABI = config.shareTokenABI!;
113
+ this.symbolList = SYMBOL_LIST;
114
+ this.priceFeedGetter = new PriceFeeds(this, config.priceFeedConfigNetwork);
115
+ }
116
+
117
+ protected async initContractsAndData(signerOrProvider: Signer | Provider, overrides?: CallOverrides) {
118
+ this.signerOrProvider = signerOrProvider;
119
+ // check network
120
+ let network: Network;
121
+ try {
122
+ if (signerOrProvider instanceof Signer) {
123
+ network = await signerOrProvider.provider!.getNetwork();
124
+ } else {
125
+ network = await signerOrProvider.getNetwork();
126
+ }
127
+ } catch (error: any) {
128
+ console.log(error);
129
+ throw new Error(`Unable to connect to network.`);
130
+ }
131
+ if (network.chainId !== this.chainId) {
132
+ throw new Error(`Provider: chain id ${network.chainId} does not match config (${this.chainId})`);
133
+ }
134
+ this.proxyContract = IPerpetualManager__factory.connect(this.proxyAddr, signerOrProvider);
135
+
136
+ this.lobFactoryAddr = await this.proxyContract.getOrderBookFactoryAddress(overrides || {});
137
+ this.lobFactoryContract = LimitOrderBookFactory__factory.connect(this.lobFactoryAddr, signerOrProvider);
138
+ await this._fillSymbolMaps(overrides);
139
+ }
140
+
141
+ /**
142
+ * Returns the order-book contract for the symbol if found or fails
143
+ * @param symbol symbol of the form ETH-USD-MATIC
144
+ * @returns order book contract for the perpetual
145
+ */
146
+ public getOrderBookContract(symbol: string): Contract & LimitOrderBook {
147
+ let orderBookAddr = this.symbolToPerpStaticInfo.get(symbol)?.limitOrderBookAddr;
148
+ if (orderBookAddr == "" || orderBookAddr == undefined || this.signerOrProvider == null) {
149
+ throw Error(`no limit order book found for ${symbol} or no signer`);
150
+ }
151
+ let lobContract = LimitOrderBook__factory.connect(orderBookAddr, this.signerOrProvider);
152
+ return lobContract;
153
+ }
154
+
155
+ /**
156
+ * Called when initializing. This function fills this.symbolToTokenAddrMap,
157
+ * and this.nestedPerpetualIDs and this.symbolToPerpStaticInfo
158
+ *
159
+ */
160
+ protected async _fillSymbolMaps(overrides?: CallOverrides) {
161
+ if (!this.proxyContract || !this.lobFactoryContract) {
162
+ throw Error("proxy or limit order book not defined");
163
+ }
164
+ let poolInfo = await PerpetualDataHandler.getPoolStaticInfo(this.proxyContract, overrides);
165
+
166
+ this.nestedPerpetualIDs = poolInfo.nestedPerpetualIDs;
167
+
168
+ for (let j = 0; j < poolInfo.nestedPerpetualIDs.length; j++) {
169
+ let info: PoolStaticInfo = {
170
+ poolId: j + 1,
171
+ poolMarginSymbol: "", //fill later
172
+ poolMarginTokenAddr: poolInfo.poolMarginTokenAddr[j],
173
+ shareTokenAddr: poolInfo.poolShareTokenAddr[j],
174
+ oracleFactoryAddr: poolInfo.oracleFactory,
175
+ isRunning: poolInfo.poolShareTokenAddr[j] != AddressZero,
176
+ };
177
+ this.poolStaticInfos.push(info);
178
+ }
179
+ let perpStaticInfos = await PerpetualDataHandler.getPerpetualStaticInfo(
180
+ this.proxyContract,
181
+ this.nestedPerpetualIDs,
182
+ this.symbolList,
183
+ overrides
184
+ );
185
+
186
+ let requiredPairs = new Set<string>();
187
+ // 1) determine pool currency based on its perpetuals
188
+ // 2) determine which triangulations we need
189
+ // 3) fill mapping this.symbolToPerpStaticInf
190
+ for (let j = 0; j < perpStaticInfos.length; j++) {
191
+ const perp = perpStaticInfos[j];
192
+ requiredPairs.add(perp.S2Symbol);
193
+ if (perp.S3Symbol != "") {
194
+ requiredPairs.add(perp.S3Symbol);
195
+ }
196
+ let poolCCY = this.poolStaticInfos[perp.poolId - 1].poolMarginSymbol;
197
+ if (poolCCY == "") {
198
+ //not already filled
199
+ const [base, quote] = perp.S2Symbol.split("-");
200
+ const base3 = perp.S3Symbol.split("-")[0];
201
+ // we find out the pool currency by looking at all perpetuals
202
+ // from the perpetual.
203
+ if (perp.collateralCurrencyType == COLLATERAL_CURRENCY_BASE) {
204
+ poolCCY = base;
205
+ } else if (perp.collateralCurrencyType == COLLATERAL_CURRENCY_QUOTE) {
206
+ poolCCY = quote;
207
+ } else {
208
+ poolCCY = base3;
209
+ }
210
+ // set pool currency
211
+ this.poolStaticInfos[perp.poolId - 1].poolMarginSymbol = poolCCY;
212
+ // push pool margin token address into map
213
+ this.symbolToTokenAddrMap.set(poolCCY, this.poolStaticInfos[perp.poolId - 1].poolMarginTokenAddr);
214
+ }
215
+ let currentSymbol3 = perp.S2Symbol + "-" + poolCCY;
216
+ this.symbolToPerpStaticInfo.set(currentSymbol3, perpStaticInfos[j]);
217
+ }
218
+ // pre-calculate all triangulation paths so we can easily get from
219
+ // the prices of price-feeds to the index price required, e.g.
220
+ // BTC-USDC : BTC-USD / USDC-USD
221
+ this.priceFeedGetter.initializeTriangulations(requiredPairs);
222
+
223
+ // fill this.perpetualIdToSymbol
224
+ for (let [key, info] of this.symbolToPerpStaticInfo) {
225
+ this.perpetualIdToSymbol.set(info.id, key);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Get pool symbol given a pool Id.
231
+ * @param {number} poolId Pool Id.
232
+ * @returns {symbol} Pool symbol, e.g. "USDC".
233
+ */
234
+ public getSymbolFromPoolId(poolId: number): string {
235
+ return PerpetualDataHandler._getSymbolFromPoolId(poolId, this.poolStaticInfos);
236
+ }
237
+
238
+ /**
239
+ * Get pool Id given a pool symbol. Pool IDs start at 1.
240
+ * @param {string} symbol Pool symbol.
241
+ * @returns {number} Pool Id.
242
+ */
243
+ public getPoolIdFromSymbol(symbol: string): number {
244
+ return PerpetualDataHandler._getPoolIdFromSymbol(symbol, this.poolStaticInfos);
245
+ }
246
+
247
+ /**
248
+ * Get perpetual Id given a perpetual symbol.
249
+ * @param {string} symbol Perpetual symbol, e.g. "BTC-USD-MATIC".
250
+ * @returns {number} Perpetual Id.
251
+ */
252
+ public getPerpIdFromSymbol(symbol: string): number {
253
+ return PerpetualDataHandler.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
254
+ }
255
+
256
+ /**
257
+ * Get the symbol in long format of the perpetual id
258
+ * @param perpId perpetual id
259
+ */
260
+ public getSymbolFromPerpId(perpId: number): string | undefined {
261
+ return this.perpetualIdToSymbol.get(perpId);
262
+ }
263
+
264
+ public symbol4BToLongSymbol(sym: string): string {
265
+ return symbol4BToLongSymbol(sym, this.symbolList);
266
+ }
267
+
268
+ /**
269
+ * Get PriceFeedSubmission data required for blockchain queries that involve price data, and the corresponding
270
+ * triangulated prices for the indices S2 and S3
271
+ * @param symbol pool symbol of the form "ETH-USD-MATIC"
272
+ * @returns PriceFeedSubmission and prices for S2 and S3. [S2price, 0] if S3 not defined.
273
+ */
274
+ public async fetchPriceSubmissionInfoForPerpetual(
275
+ symbol: string
276
+ ): Promise<{ submission: PriceFeedSubmission; pxS2S3: [number, number] }> {
277
+ // fetch prices from required price-feeds (REST)
278
+ return await this.priceFeedGetter.fetchFeedPriceInfoAndIndicesForPerpetual(symbol);
279
+ }
280
+
281
+ /**
282
+ * Get the symbols required as indices for the given perpetual
283
+ * @param symbol of the form ETH-USD-MATIC, specifying the perpetual
284
+ * @returns name of underlying index prices, e.g. ["MATIC-USD", ""]
285
+ */
286
+ public getIndexSymbols(symbol: string): [string, string] {
287
+ // get index
288
+ let staticInfo = this.symbolToPerpStaticInfo.get(symbol);
289
+ if (staticInfo == undefined) {
290
+ throw new Error(`No static info for perpetual with symbol ${symbol}`);
291
+ }
292
+ return [staticInfo.S2Symbol, staticInfo.S3Symbol];
293
+ }
294
+
295
+ /**
296
+ * Get the latest prices for a given perpetual from the offchain oracle
297
+ * networks
298
+ * @param symbol perpetual symbol of the form BTC-USD-MATIC
299
+ * @returns array of price feed updates that can be submitted to the smart contract
300
+ * and corresponding price information
301
+ */
302
+ public async fetchLatestFeedPriceInfo(symbol: string): Promise<PriceFeedSubmission> {
303
+ return await this.priceFeedGetter.fetchLatestFeedPriceInfoForPerpetual(symbol);
304
+ }
305
+
306
+ /**
307
+ * Get list of required pyth price source IDs for given perpetual
308
+ * @param symbol perpetual symbol, e.g., BTC-USD-MATIC
309
+ * @returns list of required pyth price sources for this perpetual
310
+ */
311
+ public getPriceIds(symbol: string): string[] {
312
+ let perpInfo = this.symbolToPerpStaticInfo.get(symbol);
313
+ if (perpInfo == undefined) {
314
+ throw Error(`Perpetual with symbol ${symbol} not found. Check symbol or use createProxyInstance().`);
315
+ }
316
+ return perpInfo.priceIds;
317
+ }
318
+
319
+ protected static _getSymbolFromPoolId(poolId: number, staticInfos: PoolStaticInfo[]): string {
320
+ let idx = poolId - 1;
321
+ return staticInfos[idx].poolMarginSymbol;
322
+ }
323
+
324
+ protected static _getPoolIdFromSymbol(symbol: string, staticInfos: PoolStaticInfo[]): number {
325
+ let symbols = symbol.split("-");
326
+ //in case user provided ETH-USD-MATIC instead of MATIC; or similar
327
+ if (symbols.length == 3) {
328
+ symbol = symbols[2];
329
+ }
330
+ let j = 0;
331
+ while (j < staticInfos.length && staticInfos[j].poolMarginSymbol != symbol) {
332
+ j++;
333
+ }
334
+ if (j == staticInfos.length) {
335
+ throw new Error(`no pool found for symbol ${symbol}`);
336
+ }
337
+ return j + 1;
338
+ }
339
+
340
+ /**
341
+ * Get perpetual symbols for a given pool
342
+ * @param poolSymbol pool symbol such as "MATIC"
343
+ * @returns array of perpetual symbols in this pool
344
+ */
345
+ public getPerpetualSymbolsInPool(poolSymbol: string): string[] {
346
+ const j = PerpetualDataHandler._getPoolIdFromSymbol(poolSymbol, this.poolStaticInfos);
347
+ const perpIds = this.nestedPerpetualIDs[j - 1];
348
+ const perpSymbols = perpIds.map((k) => {
349
+ let s = this.getSymbolFromPerpId(k);
350
+ if (s == undefined) {
351
+ return "";
352
+ }
353
+ return s;
354
+ });
355
+ return perpSymbols;
356
+ }
357
+
358
+ public getNestedPerpetualIds(): number[][] {
359
+ return this.nestedPerpetualIDs;
360
+ }
361
+
362
+ /**
363
+ * Collect all perpetuals static info
364
+ * @param {ethers.Contract} _proxyContract perpetuals contract with getter
365
+ * @param {Array<Array<number>>} nestedPerpetualIDs perpetual id-array for each pool
366
+ * @param {Map<string, string>} symbolList mapping of symbols to convert long-format <-> blockchain-format
367
+ * @returns array with PerpetualStaticInfo for each perpetual
368
+ */
369
+ public static async getPerpetualStaticInfo(
370
+ _proxyContract: IPerpetualManager,
371
+ nestedPerpetualIDs: Array<Array<number>>,
372
+ symbolList: Map<string, string>,
373
+ overrides?: CallOverrides
374
+ ): Promise<Array<PerpetualStaticInfo>> {
375
+ // flatten perpetual ids into chunks
376
+ const chunkSize = 10;
377
+ let ids = PerpetualDataHandler.nestedIDsToChunks(chunkSize, nestedPerpetualIDs);
378
+ // query blockchain in chunks
379
+ const infoArr = new Array<PerpetualStaticInfo>();
380
+ for (let k = 0; k < ids.length; k++) {
381
+ let perpInfos = await _proxyContract.getPerpetualStaticInfo(ids[k], overrides || {});
382
+ for (let j = 0; j < perpInfos.length; j++) {
383
+ let base = contractSymbolToSymbol(perpInfos[j].S2BaseCCY, symbolList);
384
+ let quote = contractSymbolToSymbol(perpInfos[j].S2QuoteCCY, symbolList);
385
+ let base3 = contractSymbolToSymbol(perpInfos[j].S3BaseCCY, symbolList);
386
+ let quote3 = contractSymbolToSymbol(perpInfos[j].S3QuoteCCY, symbolList);
387
+ let sym2 = base + "-" + quote;
388
+ let sym3 = base3 == "" ? "" : base3 + "-" + quote3;
389
+ let info: PerpetualStaticInfo = {
390
+ id: perpInfos[j].id,
391
+ poolId: Math.floor(perpInfos[j].id / 100_000), //uint24(_iPoolId) * 100_000 + iPerpetualIndex;
392
+ limitOrderBookAddr: perpInfos[j].limitOrderBookAddr,
393
+ initialMarginRate: ABDK29ToFloat(perpInfos[j].fInitialMarginRate),
394
+ maintenanceMarginRate: ABDK29ToFloat(perpInfos[j].fMaintenanceMarginRate),
395
+ collateralCurrencyType: perpInfos[j].collCurrencyType,
396
+ S2Symbol: sym2,
397
+ S3Symbol: sym3,
398
+ lotSizeBC: ABK64x64ToFloat(perpInfos[j].fLotSizeBC),
399
+ referralRebate: ABK64x64ToFloat(perpInfos[j].fReferralRebateCC),
400
+ priceIds: perpInfos[j].priceIds,
401
+ };
402
+ infoArr.push(info);
403
+ }
404
+ }
405
+ return infoArr;
406
+ }
407
+
408
+ /**
409
+ * Breaks up an array of nested arrays into chunks of a specified size.
410
+ * @param {number} chunkSize The size of each chunk.
411
+ * @param {number[][]} nestedIDs The array of nested arrays to chunk.
412
+ * @returns {number[][]} An array of subarrays, each containing `chunkSize` or fewer elements from `nestedIDs`.
413
+ */
414
+ public static nestedIDsToChunks(chunkSize: number, nestedIDs: Array<Array<number>>): Array<Array<number>> {
415
+ const chunkIDs: number[][] = [];
416
+ let currentChunk: number[] = [];
417
+ for (let k = 0; k < nestedIDs.length; k++) {
418
+ const currentPoolIds = nestedIDs[k];
419
+ for (let j = 0; j < currentPoolIds.length; j++) {
420
+ currentChunk.push(currentPoolIds[j]);
421
+ if (currentChunk.length === chunkSize) {
422
+ chunkIDs.push(currentChunk);
423
+ currentChunk = [];
424
+ }
425
+ }
426
+ }
427
+ if (currentChunk.length > 0) {
428
+ chunkIDs.push(currentChunk);
429
+ }
430
+ return chunkIDs;
431
+ }
432
+
433
+ public static async getPoolStaticInfo(
434
+ _proxyContract: IPerpetualManager,
435
+ overrides?: CallOverrides
436
+ ): Promise<{
437
+ nestedPerpetualIDs: Array<Array<number>>;
438
+ poolShareTokenAddr: Array<string>;
439
+ poolMarginTokenAddr: Array<string>;
440
+ oracleFactory: string;
441
+ }> {
442
+ let idxFrom = 1;
443
+ const len = 10;
444
+ let lenReceived = 10;
445
+ let nestedPerpetualIDs: Array<Array<number>> = [];
446
+ let poolShareTokenAddr: Array<string> = [];
447
+ let poolMarginTokenAddr: Array<string> = [];
448
+ let oracleFactory: string = "";
449
+ while (lenReceived == len) {
450
+ let res = await _proxyContract.getPoolStaticInfo(idxFrom, idxFrom + len - 1, overrides || {});
451
+ lenReceived = res.length;
452
+ nestedPerpetualIDs = nestedPerpetualIDs.concat(res[0]);
453
+ poolShareTokenAddr = res[1];
454
+ poolMarginTokenAddr = res[2];
455
+ oracleFactory = res[3];
456
+ idxFrom = idxFrom + len;
457
+ }
458
+ return {
459
+ nestedPerpetualIDs: nestedPerpetualIDs,
460
+ poolShareTokenAddr: poolShareTokenAddr,
461
+ poolMarginTokenAddr: poolMarginTokenAddr,
462
+ oracleFactory: oracleFactory,
463
+ };
464
+ }
465
+
466
+ public static buildMarginAccountFromState(
467
+ symbol: string,
468
+ traderState: BigNumber[],
469
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
470
+ _pxS2S3: [number, number]
471
+ ): MarginAccount {
472
+ const idx_cash = 3;
473
+ const idx_notional = 4;
474
+ const idx_locked_in = 5;
475
+ const idx_mark_price = 8;
476
+ const idx_lvg = 7;
477
+ const idx_s3 = 9;
478
+ let isEmpty = traderState[idx_notional].eq(0);
479
+ let cash = ABK64x64ToFloat(traderState[idx_cash]);
480
+ let S2Liq = 0,
481
+ S3Liq = 0,
482
+ tau = Infinity,
483
+ pnl = 0,
484
+ unpaidFundingCC = 0,
485
+ fLockedIn = BigNumber.from(0),
486
+ side = CLOSED_SIDE,
487
+ entryPrice = 0;
488
+ if (!isEmpty) {
489
+ [S2Liq, S3Liq, tau, pnl, unpaidFundingCC] = PerpetualDataHandler._calculateLiquidationPrice(
490
+ symbol,
491
+ traderState,
492
+ _pxS2S3[0],
493
+ symbolToPerpStaticInfo
494
+ );
495
+ fLockedIn = traderState[idx_locked_in];
496
+ side = traderState[idx_locked_in].gt(0) ? BUY_SIDE : SELL_SIDE;
497
+ entryPrice = ABK64x64ToFloat(div64x64(fLockedIn, traderState[idx_notional]));
498
+ }
499
+ let mgn: MarginAccount = {
500
+ symbol: symbol,
501
+ positionNotionalBaseCCY: isEmpty ? 0 : ABK64x64ToFloat(traderState[idx_notional].abs()),
502
+ side: isEmpty ? CLOSED_SIDE : side,
503
+ entryPrice: isEmpty ? 0 : entryPrice,
504
+ leverage: isEmpty ? 0 : ABK64x64ToFloat(traderState[idx_lvg]),
505
+ markPrice: ABK64x64ToFloat(traderState[idx_mark_price].abs()),
506
+ unrealizedPnlQuoteCCY: isEmpty ? 0 : pnl,
507
+ unrealizedFundingCollateralCCY: isEmpty ? 0 : unpaidFundingCC,
508
+ collateralCC: cash,
509
+ liquidationLvg: isEmpty ? 0 : 1 / tau,
510
+ liquidationPrice: isEmpty ? [0, 0] : [S2Liq, S3Liq],
511
+ collToQuoteConversion: ABK64x64ToFloat(traderState[idx_s3]),
512
+ };
513
+ return mgn;
514
+ }
515
+
516
+ public static async getMarginAccount(
517
+ traderAddr: string,
518
+ symbol: string,
519
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
520
+ _proxyContract: IPerpetualManager,
521
+ _pxS2S3: [number, number],
522
+ overrides?: CallOverrides
523
+ ): Promise<MarginAccount> {
524
+ let perpId = Number(symbol);
525
+ if (isNaN(perpId)) {
526
+ perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
527
+ }
528
+ let traderState = await _proxyContract.getTraderState(
529
+ perpId,
530
+ traderAddr,
531
+ _pxS2S3.map((x) => floatToABK64x64(x)) as [BigNumber, BigNumber],
532
+ overrides || {}
533
+ );
534
+ return PerpetualDataHandler.buildMarginAccountFromState(symbol, traderState, symbolToPerpStaticInfo, _pxS2S3);
535
+ }
536
+
537
+ protected static async _queryPerpetualPrice(
538
+ symbol: string,
539
+ tradeAmount: number,
540
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
541
+ _proxyContract: IPerpetualManager,
542
+ indexPrices: [number, number],
543
+ overrides?: CallOverrides
544
+ ): Promise<number> {
545
+ let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
546
+ let fIndexPrices = indexPrices.map((x) => floatToABK64x64(x == undefined || Number.isNaN(x) ? 0 : x));
547
+ let fPrice = await _proxyContract.queryPerpetualPrice(
548
+ perpId,
549
+ floatToABK64x64(tradeAmount),
550
+ fIndexPrices as [BigNumber, BigNumber],
551
+ overrides || {}
552
+ );
553
+ return ABK64x64ToFloat(fPrice);
554
+ }
555
+
556
+ protected static async _queryPerpetualMarkPrice(
557
+ symbol: string,
558
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
559
+ _proxyContract: IPerpetualManager,
560
+ indexPrices: [number, number],
561
+ overrides?: CallOverrides
562
+ ): Promise<number> {
563
+ let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
564
+ let [S2, S3] = indexPrices.map((x) => floatToABK64x64(x == undefined || Number.isNaN(x) ? 0 : x));
565
+ let ammState = await _proxyContract.getAMMState(perpId, [S2, S3], overrides || {});
566
+ // ammState[6] == S2 == indexPrices[0] up to rounding errors (indexPrices is most accurate)
567
+ return indexPrices[0] * (1 + ABK64x64ToFloat(ammState[8]));
568
+ }
569
+
570
+ protected static async _queryPerpetualState(
571
+ symbol: string,
572
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
573
+ _proxyContract: IPerpetualManager,
574
+ indexPrices: [number, number, boolean, boolean],
575
+ overrides?: CallOverrides
576
+ ): Promise<PerpetualState> {
577
+ let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
578
+ let staticInfo = symbolToPerpStaticInfo.get(symbol)!;
579
+ let ccy = symbol.split("-");
580
+ let [S2, S3] = [indexPrices[0], indexPrices[1]];
581
+ if (staticInfo.collateralCurrencyType == CollaterlCCY.BASE) {
582
+ S3 = S2;
583
+ } else if (staticInfo.collateralCurrencyType == CollaterlCCY.QUOTE) {
584
+ S3 = 1;
585
+ }
586
+ let ammState = await _proxyContract.getAMMState(
587
+ perpId,
588
+ [S2, S3].map(floatToABK64x64) as [BigNumber, BigNumber],
589
+ overrides || {}
590
+ );
591
+ let markPrice = S2 * (1 + ABK64x64ToFloat(ammState[8]));
592
+ let state: PerpetualState = {
593
+ id: perpId,
594
+ state: PERP_STATE_STR[ammState[13].toNumber()],
595
+ baseCurrency: ccy[0],
596
+ quoteCurrency: ccy[1],
597
+ indexPrice: S2,
598
+ collToQuoteIndexPrice: S3,
599
+ markPrice: markPrice,
600
+ midPrice: ABK64x64ToFloat(ammState[10]),
601
+ currentFundingRateBps: ABK64x64ToFloat(ammState[14]) * 1e4,
602
+ openInterestBC: ABK64x64ToFloat(ammState[11]),
603
+ isMarketClosed: indexPrices[2] || indexPrices[3],
604
+ };
605
+ return state;
606
+ }
607
+
608
+ /**
609
+ * Liquidation price
610
+ * @param symbol symbol of the form BTC-USD-MATIC
611
+ * @param traderState BigInt array according to smart contract
612
+ * @param S2 number, index price S2
613
+ * @param symbolToPerpStaticInfo mapping symbol->PerpStaticInfo
614
+ * @returns liquidation mark-price, corresponding collateral/quote conversion
615
+ */
616
+ protected static _calculateLiquidationPrice(
617
+ symbol: string,
618
+ traderState: BigNumber[],
619
+ S2: number,
620
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
621
+ ): [number, number, number, number, number] {
622
+ const idx_availableCashCC = 2;
623
+ const idx_cash = 3;
624
+ const idx_notional = 4;
625
+ const idx_locked_in = 5;
626
+ const idx_mark_price = 8;
627
+ const idx_s3 = 9;
628
+ // const idx_s2 = 10;
629
+ let S2Liq: number;
630
+ let S3Liq: number = ABK64x64ToFloat(traderState[idx_s3]);
631
+ let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(symbol);
632
+ if (perpInfo == undefined) {
633
+ throw new Error(`no info for perpetual ${symbol}`);
634
+ }
635
+ let tau = perpInfo.maintenanceMarginRate;
636
+ let lockedInValueQC = ABK64x64ToFloat(traderState[idx_locked_in]);
637
+ let position = ABK64x64ToFloat(traderState[idx_notional]);
638
+ let cashCC = ABK64x64ToFloat(traderState[idx_availableCashCC]);
639
+ let Sm = ABK64x64ToFloat(traderState[idx_mark_price]);
640
+ let unpaidFundingCC = ABK64x64ToFloat(traderState[idx_availableCashCC].sub(traderState[idx_cash]));
641
+ let unpaidFunding = unpaidFundingCC;
642
+
643
+ if (perpInfo.collateralCurrencyType == CollaterlCCY.BASE) {
644
+ S2Liq = calculateLiquidationPriceCollateralBase(lockedInValueQC, position, cashCC, tau);
645
+ S3Liq = S2Liq;
646
+ unpaidFunding = unpaidFunding / S2;
647
+ } else if (perpInfo.collateralCurrencyType == CollaterlCCY.QUANTO) {
648
+ let S3 = S3Liq;
649
+ S3Liq = S3;
650
+ S2Liq = calculateLiquidationPriceCollateralQuanto(lockedInValueQC, position, cashCC, tau, S3, Sm);
651
+ unpaidFunding = unpaidFunding / S3;
652
+ } else {
653
+ S2Liq = calculateLiquidationPriceCollateralQuote(lockedInValueQC, position, cashCC, tau);
654
+ }
655
+ // floor at 0
656
+ S2Liq = S2Liq < 0 ? 0 : S2Liq;
657
+ S3Liq = S3Liq && S3Liq < 0 ? 0 : S3Liq;
658
+ // account cash + pnl = avail cash + pos Sm - L = margin balance
659
+ let pnl = position * Sm - lockedInValueQC + unpaidFunding;
660
+ return [S2Liq, S3Liq, tau, pnl, unpaidFundingCC];
661
+ }
662
+
663
+ /**
664
+ * Finds the perpetual id for a symbol of the form
665
+ * <base>-<quote>-<collateral>. The function first converts the
666
+ * token names into bytes4 representation
667
+ * @param symbol symbol (e.g., BTC-USD-MATC)
668
+ * @param symbolToPerpStaticInfo map that contains the bytes4-symbol to PerpetualStaticInfo
669
+ * including id mapping
670
+ * @returns perpetual id or it fails
671
+ */
672
+ protected static symbolToPerpetualId(
673
+ symbol: string,
674
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
675
+ ): number {
676
+ let id = symbolToPerpStaticInfo.get(symbol)?.id;
677
+ if (id == undefined) {
678
+ throw Error(`No perpetual found for symbol ${symbol}`);
679
+ }
680
+ return id;
681
+ }
682
+
683
+ protected static symbolToBytes4Symbol(symbol: string): string {
684
+ //split by dashes BTC-USD-MATIC
685
+ let symbols: string[] = symbol.split("-");
686
+ if (symbols.length != 3) {
687
+ throw Error(`Symbol ${symbol} not valid. Expecting CCY-CCY-CCY format`);
688
+ }
689
+ //transform into bytes4 currencies (without the space): "BTC", "USD", "MATC"
690
+ symbols = symbols.map((x) => {
691
+ let v = to4Chars(x);
692
+ v = v.replace(/\0/g, "");
693
+ return v;
694
+ });
695
+ // concatenate and find perpetual Id in map
696
+ return symbols[0] + "-" + symbols[1] + "-" + symbols[2];
697
+ }
698
+
699
+ private static _getByValue(map: any, searchValue: any, valueField: any) {
700
+ for (let [key, value] of map.entries()) {
701
+ if (value[valueField] === searchValue) {
702
+ return key;
703
+ }
704
+ }
705
+ return undefined;
706
+ }
707
+
708
+ protected static fromSmartContractOrder(
709
+ order: SmartContractOrder | IPerpetualOrder.OrderStruct,
710
+ symbolToPerpInfoMap: Map<string, PerpetualStaticInfo>
711
+ ): Order {
712
+ // find symbol of perpetual id
713
+ let symbol = PerpetualDataHandler._getByValue(symbolToPerpInfoMap, order.iPerpetualId, "id");
714
+ if (symbol == undefined) {
715
+ throw Error(`Perpetual id ${order.iPerpetualId} not found. Check with marketData.exchangeInfo().`);
716
+ }
717
+ let side = order.fAmount > BigNumber.from(0) ? BUY_SIDE : SELL_SIDE;
718
+ let limitPrice, stopPrice;
719
+ let fLimitPrice: BigNumber | undefined = BigNumber.from(order.fLimitPrice);
720
+ if (fLimitPrice.eq(0)) {
721
+ limitPrice = side == BUY_SIDE ? undefined : 0;
722
+ } else if (fLimitPrice.eq(MAX_64x64)) {
723
+ limitPrice = side == BUY_SIDE ? Infinity : undefined;
724
+ } else {
725
+ limitPrice = ABK64x64ToFloat(fLimitPrice);
726
+ }
727
+ let fStopPrice: BigNumber | undefined = BigNumber.from(order.fTriggerPrice);
728
+ if (fStopPrice.eq(0) || fStopPrice.eq(MAX_64x64)) {
729
+ stopPrice = undefined;
730
+ } else {
731
+ stopPrice = ABK64x64ToFloat(fStopPrice);
732
+ }
733
+ let userOrder: Order = {
734
+ symbol: symbol!,
735
+ side: side,
736
+ type: PerpetualDataHandler._flagToOrderType(BigNumber.from(order.flags), BigNumber.from(order.fLimitPrice)),
737
+ quantity: Math.abs(ABK64x64ToFloat(BigNumber.from(order.fAmount))),
738
+ reduceOnly: containsFlag(BigNumber.from(order.flags), MASK_CLOSE_ONLY),
739
+ limitPrice: limitPrice,
740
+ keepPositionLvg: containsFlag(BigNumber.from(order.flags), MASK_KEEP_POS_LEVERAGE),
741
+ brokerFeeTbps: order.brokerFeeTbps == 0 ? undefined : Number(order.brokerFeeTbps),
742
+ brokerAddr: order.brokerAddr == ZERO_ADDRESS ? undefined : order.brokerAddr,
743
+ brokerSignature: order.brokerSignature == "0x" ? undefined : order.brokerSignature,
744
+ stopPrice: stopPrice,
745
+ leverage: Number(order.leverageTDR) / 100,
746
+ deadline: Number(order.iDeadline),
747
+ executionTimestamp: Number(order.executionTimestamp),
748
+ submittedTimestamp: Number(order.submittedTimestamp),
749
+ };
750
+ return userOrder;
751
+ }
752
+ /**
753
+ * Transform the convenient form of the order into a smart-contract accepted type of order
754
+ * @param order order type
755
+ * @param traderAddr address of the trader
756
+ * @param symbolToPerpetualMap mapping of symbol to perpetual Id
757
+ * @returns SmartContractOrder
758
+ */
759
+ protected static toSmartContractOrder(
760
+ order: Order,
761
+ traderAddr: string,
762
+ perpStaticInfo: Map<string, PerpetualStaticInfo>
763
+ ): SmartContractOrder {
764
+ // this revers if order is invalid
765
+ PerpetualDataHandler.checkOrder(order, perpStaticInfo);
766
+ // translate order
767
+ let flags = PerpetualDataHandler._orderTypeToFlag(order);
768
+ let brokerSig = order.brokerSignature == undefined ? [] : order.brokerSignature;
769
+ let perpetualId = PerpetualDataHandler.symbolToPerpetualId(order.symbol, perpStaticInfo);
770
+ let fAmount: BigNumber;
771
+ if (order.side == BUY_SIDE) {
772
+ fAmount = floatToABK64x64(Math.abs(order.quantity));
773
+ } else if (order.side == SELL_SIDE) {
774
+ fAmount = floatToABK64x64(-Math.abs(order.quantity));
775
+ } else {
776
+ throw Error(`invalid side in order spec, use ${BUY_SIDE} or ${SELL_SIDE}`);
777
+ }
778
+ let fLimitPrice: BigNumber;
779
+ if (order.limitPrice == undefined) {
780
+ // we need to set the limit price to infinity or zero for
781
+ // the trade to go through
782
+ // Also: stop orders always have limits set, so even for this case
783
+ // we set the limit to 0 or infinity
784
+ fLimitPrice = order.side == BUY_SIDE ? MAX_64x64 : BigNumber.from(0);
785
+ } else {
786
+ fLimitPrice = floatToABK64x64(order.limitPrice);
787
+ }
788
+
789
+ let iDeadline = order.deadline == undefined ? Date.now() / 1000 + ORDER_MAX_DURATION_SEC : order.deadline;
790
+ let fTriggerPrice = order.stopPrice == undefined ? BigNumber.from(0) : floatToABK64x64(order.stopPrice);
791
+
792
+ let smOrder: SmartContractOrder = {
793
+ flags: flags,
794
+ iPerpetualId: perpetualId,
795
+ brokerFeeTbps: order.brokerFeeTbps == undefined ? 0 : order.brokerFeeTbps,
796
+ traderAddr: traderAddr,
797
+ brokerAddr: order.brokerAddr == undefined ? ZERO_ADDRESS : order.brokerAddr,
798
+ referrerAddr: ZERO_ADDRESS,
799
+ brokerSignature: brokerSig,
800
+ fAmount: fAmount,
801
+ fLimitPrice: fLimitPrice,
802
+ fTriggerPrice: fTriggerPrice,
803
+ leverageTDR: order.leverage == undefined ? 0 : Math.round(100 * order.leverage),
804
+ iDeadline: Math.round(iDeadline),
805
+ executionTimestamp: Math.round(order.executionTimestamp),
806
+ submittedTimestamp: 0,
807
+ };
808
+ return smOrder;
809
+ }
810
+
811
+ /**
812
+ * Converts a smart contract order to a client order
813
+ * @param scOrder Smart contract order
814
+ * @param parentChildIds Optional parent-child dependency
815
+ * @returns Client order that can be submitted to the corresponding LOB
816
+ */
817
+ public static fromSmartContratOrderToClientOrder(
818
+ scOrder: SmartContractOrder,
819
+ parentChildIds?: [string, string]
820
+ ): ClientOrder {
821
+ return {
822
+ flags: scOrder.flags,
823
+ iPerpetualId: scOrder.iPerpetualId,
824
+ brokerFeeTbps: scOrder.brokerFeeTbps,
825
+ traderAddr: scOrder.traderAddr,
826
+ brokerAddr: scOrder.brokerAddr,
827
+ referrerAddr: scOrder.referrerAddr,
828
+ brokerSignature: scOrder.brokerSignature,
829
+ fAmount: scOrder.fAmount,
830
+ fLimitPrice: scOrder.fLimitPrice,
831
+ fTriggerPrice: scOrder.fTriggerPrice,
832
+ leverageTDR: scOrder.leverageTDR,
833
+ iDeadline: scOrder.iDeadline,
834
+ executionTimestamp: scOrder.executionTimestamp,
835
+ parentChildDigest1: parentChildIds ? parentChildIds[0] : ZERO_ORDER_ID,
836
+ parentChildDigest2: parentChildIds ? parentChildIds[1] : ZERO_ORDER_ID,
837
+ };
838
+ }
839
+
840
+ /**
841
+ * Converts a user-friendly order to a client order
842
+ * @param order Order
843
+ * @param parentChildIds Optional parent-child dependency
844
+ * @returns Client order that can be submitted to the corresponding LOB
845
+ */
846
+ public static toClientOrder(
847
+ order: Order,
848
+ traderAddr: string,
849
+ perpStaticInfo: Map<string, PerpetualStaticInfo>,
850
+ parentChildIds?: [string, string]
851
+ ): ClientOrder {
852
+ const scOrder = PerpetualDataHandler.toSmartContractOrder(order, traderAddr, perpStaticInfo);
853
+ return PerpetualDataHandler.fromSmartContratOrderToClientOrder(scOrder, parentChildIds);
854
+ }
855
+
856
+ /**
857
+ * Converts an order as stored in the LOB smart contract into a user-friendly order type
858
+ * @param obOrder Order-book contract order type
859
+ * @returns User friendly order struct
860
+ */
861
+ public static fromClientOrder(
862
+ obOrder: IClientOrder.ClientOrderStruct | IClientOrder.ClientOrderStructOutput,
863
+ perpStaticInfo: Map<string, PerpetualStaticInfo>
864
+ ): Order {
865
+ const scOrder = {
866
+ flags: obOrder.flags,
867
+ iPerpetualId: obOrder.iPerpetualId,
868
+ brokerFeeTbps: obOrder.brokerFeeTbps,
869
+ traderAddr: obOrder.traderAddr,
870
+ brokerAddr: obOrder.brokerAddr,
871
+ brokerSignature: obOrder.brokerSignature,
872
+ fAmount: obOrder.fAmount,
873
+ fLimitPrice: obOrder.fLimitPrice,
874
+ fTriggerPrice: obOrder.fTriggerPrice,
875
+ leverageTDR: obOrder.leverageTDR,
876
+ iDeadline: obOrder.iDeadline,
877
+ executionTimestamp: obOrder.executionTimestamp,
878
+ } as SmartContractOrder;
879
+ const order = PerpetualDataHandler.fromSmartContractOrder(scOrder, perpStaticInfo);
880
+ if (
881
+ obOrder.parentChildDigest1.toString() != ZERO_ORDER_ID ||
882
+ obOrder.parentChildDigest2.toString() != ZERO_ORDER_ID
883
+ ) {
884
+ order.parentChildOrderIds = [obOrder.parentChildDigest1.toString(), obOrder.parentChildDigest2.toString()];
885
+ }
886
+ return order;
887
+ }
888
+
889
+ private static _flagToOrderType(orderFlags: BigNumber, orderLimitPrice: BigNumber): string {
890
+ let flag = BigNumber.from(orderFlags);
891
+ let isLimit = containsFlag(flag, MASK_LIMIT_ORDER);
892
+ let hasLimit = !BigNumber.from(orderLimitPrice).eq(0) || !BigNumber.from(orderLimitPrice).eq(MAX_64x64);
893
+ let isStop = containsFlag(flag, MASK_STOP_ORDER);
894
+
895
+ if (isStop && hasLimit) {
896
+ return ORDER_TYPE_STOP_LIMIT;
897
+ } else if (isStop && !hasLimit) {
898
+ return ORDER_TYPE_STOP_MARKET;
899
+ } else if (isLimit && !isStop) {
900
+ return ORDER_TYPE_LIMIT;
901
+ } else {
902
+ return ORDER_TYPE_MARKET;
903
+ }
904
+ }
905
+
906
+ /**
907
+ * Determine the correct order flags based on the order-properties.
908
+ * Checks for some misspecifications.
909
+ * @param order order type
910
+ * @returns BigNumber flags
911
+ */
912
+ private static _orderTypeToFlag(order: Order): BigNumber {
913
+ let flag: BigNumber;
914
+ order.type = order.type.toUpperCase();
915
+ switch (order.type) {
916
+ case ORDER_TYPE_LIMIT:
917
+ flag = MASK_LIMIT_ORDER;
918
+ break;
919
+ case ORDER_TYPE_MARKET:
920
+ flag = MASK_MARKET_ORDER;
921
+ break;
922
+ case ORDER_TYPE_STOP_MARKET:
923
+ flag = MASK_STOP_ORDER;
924
+ break;
925
+ case ORDER_TYPE_STOP_LIMIT:
926
+ flag = MASK_STOP_ORDER;
927
+ break;
928
+ default: {
929
+ throw Error(`Order type ${order.type} not found.`);
930
+ }
931
+ }
932
+ if (order.keepPositionLvg != undefined && order.keepPositionLvg) {
933
+ flag = combineFlags(flag, MASK_KEEP_POS_LEVERAGE);
934
+ }
935
+ if (order.reduceOnly != undefined && order.reduceOnly) {
936
+ flag = combineFlags(flag, MASK_CLOSE_ONLY);
937
+ }
938
+ if ((order.type == ORDER_TYPE_LIMIT || order.type == ORDER_TYPE_STOP_LIMIT) && order.limitPrice == undefined) {
939
+ throw Error(`Order type ${order.type} requires limit price.`);
940
+ }
941
+ if ((order.type == ORDER_TYPE_STOP_MARKET || order.type == ORDER_TYPE_STOP_LIMIT) && order.stopPrice == undefined) {
942
+ throw Error(`Order type ${order.type} requires trigger price.`);
943
+ }
944
+ if ((order.type == ORDER_TYPE_MARKET || order.type == ORDER_TYPE_LIMIT) && order.stopPrice != undefined) {
945
+ throw Error(`Order type ${order.type} has no trigger price.`);
946
+ }
947
+ if (order.type != ORDER_TYPE_STOP_LIMIT && order.type != ORDER_TYPE_STOP_MARKET && order.stopPrice != undefined) {
948
+ throw Error(`Order type ${order.type} has no trigger price.`);
949
+ }
950
+ return flag;
951
+ }
952
+
953
+ protected static _getLotSize(symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>): number {
954
+ let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(symbol);
955
+ if (perpInfo == undefined) {
956
+ throw new Error(`no info for perpetual ${symbol}`);
957
+ }
958
+ return perpInfo.lotSizeBC;
959
+ }
960
+
961
+ protected static _getMinimalPositionSize(
962
+ symbol: string,
963
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
964
+ ): number {
965
+ return 10 * PerpetualDataHandler._getLotSize(symbol, symbolToPerpStaticInfo);
966
+ }
967
+
968
+ /**
969
+ * Get NodeSDKConfig from a chain ID, known config name, or custom file location..
970
+ * @param configNameOrfileLocation Name of a known default config, or chain ID, or json-file with required variables for config
971
+ * @param version Config version number. Defaults to highest version if name or chain ID are not unique
972
+ * @returns NodeSDKConfig
973
+ */
974
+ public static readSDKConfig(configNameOrChainIdOrFileLocation: string | number, version?: number): NodeSDKConfig {
975
+ let config: NodeSDKConfig | undefined;
976
+ if (typeof configNameOrChainIdOrFileLocation === "number") {
977
+ // user entered a chain ID
978
+ config = this.getConfigByChainId(configNameOrChainIdOrFileLocation, version);
979
+ } else if (typeof configNameOrChainIdOrFileLocation === "string") {
980
+ if (/\.json$/.test(configNameOrChainIdOrFileLocation)) {
981
+ // user entered a string that ends in .json
982
+ config = this.getConfigByLocation(configNameOrChainIdOrFileLocation);
983
+ } else {
984
+ // user entered a name
985
+ config = this.getConfigByName(configNameOrChainIdOrFileLocation, version);
986
+ }
987
+ } else {
988
+ // error
989
+ throw Error(`Please specify a chain ID, config name, or custom file location.`);
990
+ }
991
+ if (config == undefined) {
992
+ throw Error(`Config ${configNameOrChainIdOrFileLocation} not found.`);
993
+ }
994
+ return config;
995
+ }
996
+
997
+ /**
998
+ * Get a NodeSDKConfig from its name
999
+ * @param name Name of the known config
1000
+ * @param version Version of the config. Defaults to highest available.
1001
+ * @returns NodeSDKConfig
1002
+ */
1003
+ protected static getConfigByName(name: string, version?: number): NodeSDKConfig | undefined {
1004
+ let configFile = DEFAULT_CONFIG.filter((c: any) => c.name == name);
1005
+ if (configFile.length == 0) {
1006
+ throw Error(`No SDK config found with name ${name}.`);
1007
+ }
1008
+ if (configFile.length == 1) {
1009
+ return configFile[0];
1010
+ } else {
1011
+ if (version === undefined) {
1012
+ configFile = configFile.sort((conf) => -conf.version);
1013
+ return configFile[0];
1014
+ } else {
1015
+ return configFile.find((conf) => conf.version === version);
1016
+ }
1017
+ }
1018
+ }
1019
+
1020
+ /**
1021
+ * Get a NodeSDKConfig from a json file.
1022
+ * @param filename Location of the file
1023
+ * @param version Version of the config. Defaults to highest available.
1024
+ * @returns NodeSDKConfig
1025
+ */
1026
+ protected static getConfigByLocation(filename: string) {
1027
+ // file path: this throws a warning during build - that's ok, it just won't work in react apps
1028
+ // eslint-disable-next-line
1029
+ let configFile = require(filename) as NodeSDKConfig;
1030
+ loadABIs(configFile);
1031
+ return configFile;
1032
+ }
1033
+
1034
+ /**
1035
+ * Get a NodeSDKConfig from its chain Id
1036
+ * @param chainId Chain Id
1037
+ * @param version Version of the config. Defaults to highest available.
1038
+ * @returns NodeSDKConfig
1039
+ */
1040
+ protected static getConfigByChainId(chainId: number, version?: number) {
1041
+ let configFile = DEFAULT_CONFIG.filter((c: any) => c.chainId == chainId);
1042
+ if (configFile.length == 0) {
1043
+ throw Error(`No SDK config found for chain ID ${chainId}.`);
1044
+ }
1045
+ if (configFile.length == 1) {
1046
+ return configFile[0];
1047
+ } else {
1048
+ if (version === undefined) {
1049
+ configFile = configFile.sort((conf) => -conf.version);
1050
+ return configFile[0];
1051
+ } else {
1052
+ return configFile.find((conf) => conf.version === version);
1053
+ }
1054
+ }
1055
+ }
1056
+
1057
+ /**
1058
+ * Get the ABI of a function in a given contract
1059
+ * @param contract A contract instance, e.g. this.proxyContract
1060
+ * @param functionName Name of the function whose ABI we want
1061
+ * @returns Function ABI as a single JSON string
1062
+ */
1063
+ protected static _getABIFromContract(contract: Contract, functionName: string): string {
1064
+ return contract.interface.getFunction(functionName).format(FormatTypes.full);
1065
+ }
1066
+
1067
+ /**
1068
+ * Gets the pool index (starting at 0 in exchangeInfo, not ID!) corresponding to a given symbol.
1069
+ * @param symbol Symbol of the form ETH-USD-MATIC
1070
+ * @returns Pool index
1071
+ */
1072
+ public getPoolStaticInfoIndexFromSymbol(symbol: string): number {
1073
+ let pools = this.poolStaticInfos!;
1074
+ let poolId = PerpetualDataHandler._getPoolIdFromSymbol(symbol, this.poolStaticInfos);
1075
+ let k = 0;
1076
+ while (k < pools.length) {
1077
+ if (pools[k].poolId == poolId) {
1078
+ // pool found
1079
+ return k;
1080
+ }
1081
+ k++;
1082
+ }
1083
+ return -1;
1084
+ }
1085
+
1086
+ public getMarginTokenFromSymbol(symbol: string): string | undefined {
1087
+ let pools = this.poolStaticInfos!;
1088
+ let poolId = PerpetualDataHandler._getPoolIdFromSymbol(symbol, this.poolStaticInfos);
1089
+ let k = 0;
1090
+ while (k < pools.length) {
1091
+ if (pools[k].poolId == poolId) {
1092
+ // pool found
1093
+ return pools[k].poolMarginTokenAddr;
1094
+ }
1095
+ k++;
1096
+ }
1097
+ return undefined;
1098
+ }
1099
+
1100
+ /**
1101
+ * Get ABI for LimitOrderBook, Proxy, or Share Pool Token
1102
+ * @param contract name of contract: proxy|lob|sharetoken
1103
+ * @returns ABI for the requested contract
1104
+ */
1105
+ public getABI(contract: string): ContractInterface | undefined {
1106
+ switch (contract) {
1107
+ case "proxy":
1108
+ return this.proxyABI;
1109
+ case "lob":
1110
+ return this.lobABI;
1111
+ case "sharetoken":
1112
+ return this.shareTokenABI;
1113
+ default:
1114
+ return undefined;
1115
+ }
1116
+ }
1117
+
1118
+ /**
1119
+ * Performs basic validity checks on a given order
1120
+ * @param order Order struct
1121
+ * @param traderAccount Trader account
1122
+ * @param perpStaticInfo Symbol to perpetual info map
1123
+ */
1124
+ protected static checkOrder(
1125
+ order: Order,
1126
+ // traderAccount: MarginAccount,
1127
+ perpStaticInfo: Map<string, PerpetualStaticInfo>
1128
+ ) {
1129
+ // check side
1130
+ if (order.side != BUY_SIDE && order.side != SELL_SIDE) {
1131
+ throw Error(`order side must be ${BUY_SIDE} or ${SELL_SIDE}`);
1132
+ }
1133
+
1134
+ // check amount
1135
+ let lotSize = perpStaticInfo.get(order.symbol)!.lotSizeBC;
1136
+ // let curPos =
1137
+ // traderAccount.side == CLOSED_SIDE
1138
+ // ? 0
1139
+ // : (traderAccount.side == BUY_SIDE ? 1 : -1) * traderAccount.positionNotionalBaseCCY;
1140
+ // let newPos = curPos + (order.side == BUY_SIDE ? 1 : -1) * order.quantity;
1141
+ // if (Math.abs(order.quantity) < lotSize || (Math.abs(newPos) >= lotSize && Math.abs(newPos) < 10 * lotSize)) {
1142
+ if (Math.abs(order.quantity) < lotSize) {
1143
+ throw Error(`trade amount too small: ${order.quantity} ${perpStaticInfo.get(order.symbol)!.S2Symbol}`);
1144
+ }
1145
+
1146
+ // check limit price
1147
+ if (order.side == BUY_SIDE && order.limitPrice != undefined && order.limitPrice <= 0) {
1148
+ throw Error(`invalid limit price for buy order: ${order.limitPrice}`);
1149
+ }
1150
+
1151
+ // broker fee
1152
+ if (order.brokerFeeTbps != undefined && order.brokerFeeTbps < 0) {
1153
+ throw Error(`invalid broker fee: ${order.brokerFeeTbps / 10} bps`);
1154
+ }
1155
+
1156
+ // stop price
1157
+ if (order.stopPrice != undefined && order.stopPrice < 0) {
1158
+ throw Error(`invalid stop price: ${order.stopPrice}`);
1159
+ }
1160
+ }
1161
+ }