@d8x/perpetuals-sdk 0.6.4 → 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 +2 -1
  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,1310 @@
1
+ import { BigNumber } from "@ethersproject/bignumber";
2
+ import { CallOverrides, Contract } from "@ethersproject/contracts";
3
+ import { Provider, StaticJsonRpcProvider } from "@ethersproject/providers";
4
+ import { formatUnits } from "@ethersproject/units";
5
+ import { ERC20__factory, LimitOrderBook } from "./contracts";
6
+ import { IClientOrder } from "./contracts/LimitOrderBook";
7
+ import {
8
+ ABK64x64ToFloat,
9
+ calculateLiquidationPriceCollateralBase,
10
+ calculateLiquidationPriceCollateralQuanto,
11
+ calculateLiquidationPriceCollateralQuote,
12
+ floatToABK64x64,
13
+ getDepositAmountForLvgTrade,
14
+ dec18ToFloat,
15
+ } from "./d8XMath";
16
+ import {
17
+ BUY_SIDE,
18
+ ClientOrder,
19
+ CLOSED_SIDE,
20
+ COLLATERAL_CURRENCY_BASE,
21
+ COLLATERAL_CURRENCY_QUANTO,
22
+ CollaterlCCY,
23
+ ERC20_ABI,
24
+ ExchangeInfo,
25
+ MarginAccount,
26
+ NodeSDKConfig,
27
+ Order,
28
+ PerpetualState,
29
+ PerpetualStaticInfo,
30
+ PERP_STATE_STR,
31
+ PoolState,
32
+ PoolStaticInfo,
33
+ SELL_SIDE,
34
+ SmartContractOrder,
35
+ ZERO_ADDRESS,
36
+ } from "./nodeSDKTypes";
37
+ import PerpetualDataHandler from "./perpetualDataHandler";
38
+ import PriceFeeds from "./priceFeeds";
39
+ import { contractSymbolToSymbol, toBytes4 } from "./utils";
40
+
41
+ /**
42
+ * Functions to access market data (e.g., information on open orders, information on products that can be traded).
43
+ * This class requires no private key and is blockchain read-only.
44
+ * No gas required for the queries here.
45
+ * @extends PerpetualDataHandler
46
+ */
47
+ export default class MarketData extends PerpetualDataHandler {
48
+ /**
49
+ * Constructor
50
+ * @param {NodeSDKConfig} config Configuration object, see
51
+ * PerpetualDataHandler.readSDKConfig.
52
+ * @example
53
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
54
+ * async function main() {
55
+ * console.log(MarketData);
56
+ * // load configuration for testnet
57
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
58
+ * // MarketData (read only, no authentication needed)
59
+ * let mktData = new MarketData(config);
60
+ * // Create a proxy instance to access the blockchain
61
+ * await mktData.createProxyInstance();
62
+ * }
63
+ * main();
64
+ *
65
+ */
66
+ public constructor(config: NodeSDKConfig) {
67
+ super(config);
68
+ }
69
+
70
+ /**
71
+ * Initialize the marketData-Class with this function
72
+ * to create instance of D8X perpetual contract and gather information
73
+ * about perpetual currencies
74
+ * @param provider optional provider
75
+ */
76
+ public async createProxyInstance(provider?: Provider, overrides?: CallOverrides): Promise<void> {
77
+ if (provider == undefined) {
78
+ this.provider = new StaticJsonRpcProvider(this.nodeURL);
79
+ } else {
80
+ this.provider = provider;
81
+ }
82
+ await this.initContractsAndData(this.provider, overrides);
83
+ }
84
+
85
+ /**
86
+ * Get the proxy address
87
+ * @returns Address of the perpetual proxy contract
88
+ */
89
+ public getProxyAddress(): string {
90
+ if (this.proxyContract == null) {
91
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
92
+ }
93
+ return this.proxyContract.address;
94
+ }
95
+
96
+ /**
97
+ * Convert the smart contract output of an order into a convenient format of type "Order"
98
+ * @param smOrder SmartContractOrder, as obtained e.g., by PerpetualLimitOrderCreated event
99
+ * @returns more convenient format of order, type "Order"
100
+ */
101
+ public smartContractOrderToOrder(smOrder: SmartContractOrder): Order {
102
+ return PerpetualDataHandler.fromSmartContractOrder(smOrder, this.symbolToPerpStaticInfo);
103
+ }
104
+
105
+ /**
106
+ * Get contract instance. Useful for event listening.
107
+ * @example
108
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
109
+ * async function main() {
110
+ * console.log(MarketData);
111
+ * // setup
112
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
113
+ * let mktData = new MarketData(config);
114
+ * await mktData.createProxyInstance();
115
+ * // Get contract instance
116
+ * let proxy = await mktData.getReadOnlyProxyInstance();
117
+ * console.log(proxy);
118
+ * }
119
+ * main();
120
+ *
121
+ * @returns read-only proxy instance
122
+ */
123
+ public getReadOnlyProxyInstance(): Contract {
124
+ if (this.proxyContract == null) {
125
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
126
+ }
127
+ return this.proxyContract;
128
+ }
129
+
130
+ /**
131
+ * Information about the products traded in the exchange.
132
+ * @example
133
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
134
+ * async function main() {
135
+ * console.log(MarketData);
136
+ * // setup
137
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
138
+ * let mktData = new MarketData(config);
139
+ * await mktData.createProxyInstance();
140
+ * // Get exchange info
141
+ * let info = await mktData.exchangeInfo();
142
+ * console.log(info);
143
+ * }
144
+ * main();
145
+ *
146
+ * @returns {ExchangeInfo} Array of static data for all the pools and perpetuals in the system.
147
+ */
148
+ public async exchangeInfo(overrides?: CallOverrides): Promise<ExchangeInfo> {
149
+ if (this.proxyContract == null) {
150
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
151
+ }
152
+ return await MarketData._exchangeInfo(
153
+ this.proxyContract,
154
+ this.poolStaticInfos,
155
+ this.symbolToPerpStaticInfo,
156
+ this.perpetualIdToSymbol,
157
+ this.nestedPerpetualIDs,
158
+ this.symbolList,
159
+ this.priceFeedGetter,
160
+ overrides
161
+ );
162
+ }
163
+
164
+ /**
165
+ * All open orders for a trader-address and a symbol.
166
+ * @param {string} traderAddr Address of the trader for which we get the open orders.
167
+ * @param {string} symbol Symbol of the form ETH-USD-MATIC or a pool symbol.
168
+ * @example
169
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
170
+ * async function main() {
171
+ * console.log(MarketData);
172
+ * // setup
173
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
174
+ * let mktData = new MarketData(config);
175
+ * await mktData.createProxyInstance();
176
+ * // Get all open orders for a trader/symbol
177
+ * let opOrder = await mktData.openOrders("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
178
+ * "ETH-USD-MATIC");
179
+ * console.log(opOrder);
180
+ * }
181
+ * main();
182
+ *
183
+ * @returns For each perpetual an array of open orders and corresponding order-ids.
184
+ */
185
+ public async openOrders(
186
+ traderAddr: string,
187
+ symbol: string,
188
+ overrides?: CallOverrides
189
+ ): Promise<{ orders: Order[]; orderIds: string[] }[]> {
190
+ // open orders requested only for given symbol
191
+ let resArray: Array<{ orders: Order[]; orderIds: string[] }> = [];
192
+ if (symbol.split("-").length == 1) {
193
+ // pool symbol
194
+ const symbols = this.getPerpetualSymbolsInPool(symbol);
195
+ let prom: Array<Promise<{ orders: Order[]; orderIds: string[] }>> = [];
196
+ for (let k = 0; k < symbols.length; k++) {
197
+ let p = this._openOrdersOfPerpetual(traderAddr, symbols[k], overrides);
198
+ prom.push(p);
199
+ }
200
+ resArray = await Promise.all(prom);
201
+ } else {
202
+ let res = await this._openOrdersOfPerpetual(traderAddr, symbol, overrides);
203
+ resArray.push(res!);
204
+ }
205
+ return resArray;
206
+ }
207
+
208
+ /**
209
+ * All open orders for a trader-address and a given perpetual symbol.
210
+ * @param {string} traderAddr Address of the trader for which we get the open orders.
211
+ * @param {string} symbol perpetual-symbol of the form ETH-USD-MATIC
212
+ * @returns open orders and order ids
213
+ */
214
+ private async _openOrdersOfPerpetual(
215
+ traderAddr: string,
216
+ symbol: string,
217
+ overrides?: CallOverrides
218
+ ): Promise<{ orders: Order[]; orderIds: string[] }> {
219
+ // open orders requested only for given symbol
220
+ let orderBookContract = this.getOrderBookContract(symbol);
221
+ let [orders, digests] = await Promise.all([
222
+ this.openOrdersOnOrderBook(traderAddr, orderBookContract, overrides),
223
+ MarketData.orderIdsOfTrader(traderAddr, orderBookContract, overrides),
224
+ ]);
225
+ return { orders: orders, orderIds: digests };
226
+ }
227
+
228
+ /**
229
+ * Information about the position open by a given trader in a given perpetual contract, or
230
+ * for all perpetuals in a pool
231
+ * @param {string} traderAddr Address of the trader for which we get the position risk.
232
+ * @param {string} symbol Symbol of the form ETH-USD-MATIC or pool symbol ("MATIC")
233
+ * @example
234
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
235
+ * async function main() {
236
+ * console.log(MarketData);
237
+ * // setup
238
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
239
+ * let mktData = new MarketData(config);
240
+ * await mktData.createProxyInstance();
241
+ * // Get position risk info
242
+ * let posRisk = await mktData.positionRisk("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
243
+ * "ETH-USD-MATIC");
244
+ * console.log(posRisk);
245
+ * }
246
+ * main();
247
+ *
248
+ * @returns {MarginAccount[]} Array of position risks of trader.
249
+ */
250
+ public async positionRisk(traderAddr: string, symbol: string, overrides?: CallOverrides): Promise<MarginAccount[]> {
251
+ if (this.proxyContract == null) {
252
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
253
+ }
254
+ let resArray: Array<MarginAccount> = [];
255
+ if (symbol.split("-").length == 1) {
256
+ // pool symbol
257
+ const symbols = this.getPerpetualSymbolsInPool(symbol);
258
+ let prom: Array<Promise<MarginAccount>> = [];
259
+ for (let k = 0; k < symbols.length; k++) {
260
+ let p = this._positionRiskForTraderInPerpetual(traderAddr, symbols[k], overrides);
261
+ prom.push(p);
262
+ }
263
+ resArray = await Promise.all(prom);
264
+ } else {
265
+ let res = await this._positionRiskForTraderInPerpetual(traderAddr, symbol, overrides);
266
+ resArray.push(res!);
267
+ }
268
+ return resArray;
269
+ }
270
+
271
+ /**
272
+ * Information about the position open by a given trader in a given perpetual contract.
273
+ * @param {string} traderAddr Address of the trader for which we get the position risk.
274
+ * @param {string} symbol perpetual symbol of the form ETH-USD-MATIC
275
+ * @returns MarginAccount struct for the trader
276
+ */
277
+ private async _positionRiskForTraderInPerpetual(
278
+ traderAddr: string,
279
+ symbol: string,
280
+ overrides?: CallOverrides
281
+ ): Promise<MarginAccount> {
282
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
283
+ let mgnAcct = await PerpetualDataHandler.getMarginAccount(
284
+ traderAddr,
285
+ symbol,
286
+ this.symbolToPerpStaticInfo,
287
+ this.proxyContract!,
288
+ [obj.idxPrices[0], obj.idxPrices[1]],
289
+ overrides
290
+ );
291
+ return mgnAcct;
292
+ }
293
+
294
+ /**
295
+ * Estimates what the position risk will be if a given order is executed.
296
+ * @param traderAddr Address of trader
297
+ * @param order Order to be submitted
298
+ * @param account Position risk before trade
299
+ * @param indexPriceInfo Index prices and market status (open/closed)
300
+ * @returns Position risk after trade
301
+ */
302
+ public async positionRiskOnTrade(
303
+ traderAddr: string,
304
+ order: Order,
305
+ account?: MarginAccount,
306
+ indexPriceInfo?: [number, number, boolean, boolean],
307
+ overrides?: CallOverrides
308
+ ): Promise<{ newPositionRisk: MarginAccount; orderCost: number }> {
309
+ if (this.proxyContract == null) {
310
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
311
+ }
312
+ // fetch undefined data
313
+ if (account == undefined) {
314
+ account = (await this.positionRisk(traderAddr, order.symbol, overrides))[0];
315
+ }
316
+ if (indexPriceInfo == undefined) {
317
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
318
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
319
+ }
320
+
321
+ let lotSizeBC = MarketData._getLotSize(account.symbol, this.symbolToPerpStaticInfo);
322
+ // Too small, no change to account
323
+ if (Math.abs(order.quantity) < lotSizeBC) {
324
+ return { newPositionRisk: account, orderCost: 0 };
325
+ }
326
+
327
+ // Current state:
328
+ // perp (for FXs and such)
329
+ let perpetualState = await this.getPerpetualState(order.symbol, indexPriceInfo, overrides);
330
+ let [S2, S3, Sm] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice, perpetualState.markPrice];
331
+ // cash in margin account: upon trading, unpaid funding will be realized
332
+ let currentMarginCashCC = account.collateralCC;
333
+ // signed position, still correct if side is closed (==0)
334
+ let currentPositionBC = (account.side == BUY_SIDE ? 1 : -1) * account.positionNotionalBaseCCY;
335
+ // signed locked-in value
336
+ let currentLockedInQC = account.entryPrice * currentPositionBC;
337
+
338
+ // New trader state:
339
+ // signed trade amount
340
+ let tradeAmountBC = Math.abs(order.quantity) * (order.side == BUY_SIDE ? 1 : -1);
341
+ // signed position
342
+ let newPositionBC = currentPositionBC + tradeAmountBC;
343
+ if (Math.abs(newPositionBC) < 10 * lotSizeBC) {
344
+ // fully closed
345
+ tradeAmountBC = -currentPositionBC;
346
+ newPositionBC = 0;
347
+ }
348
+ let newSide = newPositionBC > 0 ? BUY_SIDE : newPositionBC < 0 ? SELL_SIDE : CLOSED_SIDE;
349
+
350
+ // price for this order = limit price (conservative) if given, else the current perp price
351
+ let tradePrice =
352
+ order.limitPrice ??
353
+ (await this.getPerpetualPrice(order.symbol, tradeAmountBC, [indexPriceInfo[0], indexPriceInfo[1]], overrides));
354
+
355
+ // fees
356
+ let poolId = PerpetualDataHandler._getPoolIdFromSymbol(order.symbol, this.poolStaticInfos);
357
+ let exchangeFeeTbps = await this.proxyContract.queryExchangeFee(
358
+ poolId,
359
+ traderAddr,
360
+ order.brokerAddr ?? ZERO_ADDRESS,
361
+ overrides || {}
362
+ );
363
+ let exchangeFeeCC = (Math.abs(tradeAmountBC) * exchangeFeeTbps * 1e-5 * S2) / S3;
364
+ let brokerFeeCC = (Math.abs(tradeAmountBC) * (order.brokerFeeTbps ?? 0) * 1e-5 * S2) / S3;
365
+ let referralFeeCC = this.symbolToPerpStaticInfo.get(account.symbol)!.referralRebate;
366
+ // Trade type:
367
+ let isClose = newPositionBC == 0 || newPositionBC * tradeAmountBC < 0;
368
+ let isOpen = newPositionBC != 0 && (currentPositionBC == 0 || tradeAmountBC * currentPositionBC > 0); // regular open, no flip
369
+ let isFlip = Math.abs(newPositionBC) > Math.abs(currentPositionBC) && !isOpen; // flip position sign, not fully closed
370
+ let keepPositionLvgOnClose = (order.keepPositionLvg ?? false) && !isOpen;
371
+
372
+ // Contract: _doMarginCollateralActions
373
+ // No collateral actions if
374
+ // 1) leverage is not set or
375
+ // 2) fully closed after trade or
376
+ // 3) is a partial closing, it doesn't flip, and keep lvg flag is not set
377
+ let traderDepositCC: number;
378
+ let targetLvg: number;
379
+ if (order.leverage == undefined || newPositionBC == 0 || (!isOpen && !isFlip && !keepPositionLvgOnClose)) {
380
+ traderDepositCC = 0;
381
+ targetLvg = 0;
382
+ } else {
383
+ // 1) opening and flipping trades need to specify a leverage: default to max if not given
384
+ // 2) for others it's ignored, set target to 0
385
+ let initialMarginRate = this.symbolToPerpStaticInfo.get(account.symbol)!.initialMarginRate;
386
+ targetLvg = isFlip || isOpen ? order.leverage ?? 1 / initialMarginRate : 0;
387
+ let [b0, pos0] = isOpen ? [0, 0] : [account.collateralCC, currentPositionBC];
388
+ traderDepositCC = getDepositAmountForLvgTrade(b0, pos0, tradeAmountBC, targetLvg, tradePrice, S3, Sm);
389
+ // fees are paid from wallet in this case
390
+ traderDepositCC += exchangeFeeCC + brokerFeeCC + referralFeeCC;
391
+ }
392
+
393
+ // Contract: _executeTrade
394
+ let deltaCashCC = (-tradeAmountBC * (tradePrice - S2)) / S3;
395
+ let deltaLockedQC = tradeAmountBC * S2;
396
+ if (isClose) {
397
+ let pnl = account.entryPrice * tradeAmountBC - deltaLockedQC;
398
+ deltaLockedQC += pnl;
399
+ deltaCashCC += pnl / S3;
400
+ }
401
+ // funding and fees
402
+ deltaCashCC = deltaCashCC + account.unrealizedFundingCollateralCCY - exchangeFeeCC - brokerFeeCC - referralFeeCC;
403
+
404
+ // New cash, locked-in, entry price & leverage after trade
405
+ let newLockedInValueQC = currentLockedInQC + deltaLockedQC;
406
+ let newMarginCashCC = currentMarginCashCC + deltaCashCC + traderDepositCC;
407
+ let newEntryPrice = newPositionBC == 0 ? 0 : Math.abs(newLockedInValueQC / newPositionBC);
408
+ let newMarginBalanceCC = newMarginCashCC + (newPositionBC * Sm - newLockedInValueQC) / S3;
409
+ let newLeverage =
410
+ newPositionBC == 0
411
+ ? 0
412
+ : newMarginBalanceCC <= 0
413
+ ? Infinity
414
+ : (Math.abs(newPositionBC) * Sm) / S3 / newMarginBalanceCC;
415
+
416
+ // Liquidation params
417
+ let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(
418
+ account.symbol,
419
+ newLockedInValueQC,
420
+ newPositionBC,
421
+ newMarginCashCC,
422
+ Sm,
423
+ S3,
424
+ this.symbolToPerpStaticInfo
425
+ );
426
+
427
+ // New position risk
428
+ let newPositionRisk: MarginAccount = {
429
+ symbol: account.symbol,
430
+ positionNotionalBaseCCY: Math.abs(newPositionBC),
431
+ side: newSide,
432
+ entryPrice: newEntryPrice,
433
+ leverage: newLeverage,
434
+ markPrice: Sm,
435
+ unrealizedPnlQuoteCCY: newPositionBC * Sm - newLockedInValueQC,
436
+ unrealizedFundingCollateralCCY: 0,
437
+ collateralCC: newMarginCashCC,
438
+ collToQuoteConversion: S3,
439
+ liquidationPrice: [S2Liq, S3Liq],
440
+ liquidationLvg: 1 / tau,
441
+ };
442
+ return { newPositionRisk: newPositionRisk, orderCost: traderDepositCC };
443
+ }
444
+
445
+ /**
446
+ * Estimates what the position risk will be if given amount of collateral is added/removed from the account.
447
+ * @param traderAddr Address of trader
448
+ * @param deltaCollateral Amount of collateral to add or remove (signed)
449
+ * @param currentPositionRisk Position risk before
450
+ * @returns {MarginAccount} Position risk after
451
+ */
452
+ public async positionRiskOnCollateralAction(
453
+ deltaCollateral: number,
454
+ account: MarginAccount,
455
+ indexPriceInfo?: [number, number, boolean, boolean],
456
+ overrides?: CallOverrides
457
+ ): Promise<MarginAccount> {
458
+ if (this.proxyContract == null) {
459
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
460
+ }
461
+ if (deltaCollateral + account.collateralCC + account.unrealizedFundingCollateralCCY < 0) {
462
+ throw Error("not enough margin to remove");
463
+ }
464
+ if (indexPriceInfo == undefined) {
465
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
466
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
467
+ }
468
+ let perpetualState = await this.getPerpetualState(account.symbol, indexPriceInfo, overrides);
469
+ let [S2, S3, Sm] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice, perpetualState.markPrice];
470
+
471
+ // no position: just increase collateral and kill liquidation vars
472
+ if (account.positionNotionalBaseCCY == 0) {
473
+ return {
474
+ symbol: account.symbol,
475
+ positionNotionalBaseCCY: account.positionNotionalBaseCCY,
476
+ side: account.side,
477
+ entryPrice: account.entryPrice,
478
+ leverage: account.leverage,
479
+ markPrice: Sm,
480
+ unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
481
+ unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
482
+ collateralCC: account.collateralCC + deltaCollateral,
483
+ collToQuoteConversion: S3,
484
+ liquidationPrice: [0, undefined],
485
+ liquidationLvg: Infinity,
486
+ };
487
+ }
488
+
489
+ let positionBC = account.positionNotionalBaseCCY * (account.side == BUY_SIDE ? 1 : -1);
490
+ let lockedInQC = account.entryPrice * positionBC;
491
+ let newMarginCashCC = account.collateralCC + deltaCollateral;
492
+ let newMarginBalanceCC =
493
+ newMarginCashCC + account.unrealizedFundingCollateralCCY + (positionBC * Sm - lockedInQC) / S3;
494
+ if (newMarginBalanceCC <= 0) {
495
+ return {
496
+ symbol: account.symbol,
497
+ positionNotionalBaseCCY: account.positionNotionalBaseCCY,
498
+ side: account.side,
499
+ entryPrice: account.entryPrice,
500
+ leverage: Infinity,
501
+ markPrice: Sm,
502
+ unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
503
+ unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
504
+ collateralCC: newMarginCashCC,
505
+ collToQuoteConversion: S3,
506
+ liquidationPrice: [S2, S3],
507
+ liquidationLvg: 0,
508
+ };
509
+ }
510
+ let newLeverage = (Math.abs(positionBC) * Sm) / S3 / newMarginBalanceCC;
511
+
512
+ // Liquidation params
513
+ let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(
514
+ account.symbol,
515
+ lockedInQC,
516
+ positionBC,
517
+ newMarginCashCC,
518
+ Sm,
519
+ S3,
520
+ this.symbolToPerpStaticInfo
521
+ );
522
+
523
+ // New position risk
524
+ let newPositionRisk: MarginAccount = {
525
+ symbol: account.symbol,
526
+ positionNotionalBaseCCY: account.positionNotionalBaseCCY,
527
+ side: account.side,
528
+ entryPrice: account.entryPrice,
529
+ leverage: newLeverage,
530
+ markPrice: Sm,
531
+ unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
532
+ unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
533
+ collateralCC: newMarginCashCC,
534
+ collToQuoteConversion: S3,
535
+ liquidationPrice: [S2Liq, S3Liq],
536
+ liquidationLvg: 1 / tau,
537
+ };
538
+ return newPositionRisk;
539
+ }
540
+
541
+ protected static _getLiquidationParams(
542
+ symbol: string,
543
+ lockedInQC: number,
544
+ signedPositionBC: number,
545
+ marginCashCC: number,
546
+ markPrice: number,
547
+ collToQuoteConversion: number,
548
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
549
+ ): [number, number | undefined, number] {
550
+ let S2Liq: number, S3Liq: number | undefined;
551
+ let tau = symbolToPerpStaticInfo.get(symbol)!.maintenanceMarginRate;
552
+ let ccyType = symbolToPerpStaticInfo.get(symbol)!.collateralCurrencyType;
553
+ if (ccyType == CollaterlCCY.BASE) {
554
+ S2Liq = calculateLiquidationPriceCollateralBase(lockedInQC, signedPositionBC, marginCashCC, tau);
555
+ S3Liq = S2Liq;
556
+ } else if (ccyType == CollaterlCCY.QUANTO) {
557
+ S3Liq = collToQuoteConversion;
558
+ S2Liq = calculateLiquidationPriceCollateralQuanto(
559
+ lockedInQC,
560
+ signedPositionBC,
561
+ marginCashCC,
562
+ tau,
563
+ collToQuoteConversion,
564
+ markPrice
565
+ );
566
+ } else {
567
+ S2Liq = calculateLiquidationPriceCollateralQuote(lockedInQC, signedPositionBC, marginCashCC, tau);
568
+ }
569
+ // floor at 0
570
+ S2Liq = S2Liq < 0 ? 0 : S2Liq;
571
+ S3Liq = S3Liq && S3Liq < 0 ? 0 : S3Liq;
572
+ return [S2Liq, S3Liq, tau];
573
+ }
574
+
575
+ /**
576
+ * Gets the wallet balance in the collateral currency corresponding to a given perpetual symbol.
577
+ * @param address Address to check
578
+ * @param symbol Symbol of the form ETH-USD-MATIC.
579
+ * @returns Balance
580
+ */
581
+ public async getWalletBalance(address: string, symbol: string, overrides?: CallOverrides): Promise<number> {
582
+ let poolIdx = this.getPoolStaticInfoIndexFromSymbol(symbol);
583
+ let marginTokenAddr = this.poolStaticInfos[poolIdx].poolMarginTokenAddr;
584
+ let token = ERC20__factory.connect(marginTokenAddr, this.provider!);
585
+ let walletBalance = await token.balanceOf(address, overrides || {});
586
+ let decimals = await token.decimals(overrides || {});
587
+ return Number(formatUnits(walletBalance, decimals));
588
+ }
589
+
590
+ /**
591
+ * Get the address' balance of the pool share token
592
+ * @param address address of the liquidity provider
593
+ * @param symbolOrId Symbol of the form ETH-USD-MATIC, or MATIC (collateral only), or Pool-Id
594
+ */
595
+ public async getPoolShareTokenBalance(
596
+ address: string,
597
+ symbolOrId: string | number,
598
+ overrides?: CallOverrides
599
+ ): Promise<number> {
600
+ let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
601
+ return this._getPoolShareTokenBalanceFromId(address, poolId, overrides);
602
+ }
603
+
604
+ /**
605
+ * Query the pool share token holdings of address
606
+ * @param address address of token holder
607
+ * @param poolId pool id
608
+ * @returns pool share token balance of address
609
+ */
610
+ private async _getPoolShareTokenBalanceFromId(
611
+ address: string,
612
+ poolId: number,
613
+ overrides?: CallOverrides
614
+ ): Promise<number> {
615
+ let shareTokenAddr = this.poolStaticInfos[poolId - 1].shareTokenAddr;
616
+ let shareToken = ERC20__factory.connect(shareTokenAddr, this.provider!);
617
+ let d18ShareTokenBalanceOfAddr = await shareToken.balanceOf(address, overrides || {});
618
+ return dec18ToFloat(d18ShareTokenBalanceOfAddr);
619
+ }
620
+
621
+ /**
622
+ * Value of pool token in collateral currency
623
+ * @param symbolOrId symbol of the form ETH-USD-MATIC, MATIC (collateral), or poolId
624
+ * @returns current pool share token price in collateral currency
625
+ */
626
+ public async getShareTokenPrice(symbolOrId: string | number, overrides?: CallOverrides): Promise<number> {
627
+ let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
628
+ const priceDec18 = await this.proxyContract!.getShareTokenPriceD18(poolId, overrides || {});
629
+ const price = dec18ToFloat(priceDec18);
630
+ return price;
631
+ }
632
+
633
+ /**
634
+ * Value of the pool share tokens for this liquidity provider
635
+ * in poolSymbol-currency (e.g. MATIC, USDC).
636
+ * @param address address of liquidity provider
637
+ * @param symbolOrId symbol of the form ETH-USD-MATIC, MATIC (collateral), or poolId
638
+ * @example
639
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
640
+ * async function main() {
641
+ * console.log(MarketData);
642
+ * // setup (authentication required, PK is an environment variable with a private key)
643
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
644
+ * let md = new MarketData(config);
645
+ * await md.createProxyInstance();
646
+ * // get value of pool share token
647
+ * let shareToken = await md.getParticipationValue(myaddress, "MATIC");
648
+ * console.log(shareToken);
649
+ * }
650
+ * main();
651
+ * @returns the value (in collateral tokens) of the pool share, #share tokens, shareTokenAddress
652
+ */
653
+ public async getParticipationValue(
654
+ address: string,
655
+ symbolOrId: string | number,
656
+ overrides?: CallOverrides
657
+ ): Promise<{ value: number; shareTokenBalance: number; poolShareToken: string }> {
658
+ let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
659
+ const shareTokens = await this._getPoolShareTokenBalanceFromId(address, poolId, overrides);
660
+ const priceDec18 = await this.proxyContract!.getShareTokenPriceD18(poolId, overrides || {});
661
+ const price = dec18ToFloat(priceDec18);
662
+ const value = price * shareTokens;
663
+ const shareTokenAddr = this.poolStaticInfos[poolId - 1].shareTokenAddr;
664
+ return {
665
+ value: value,
666
+ shareTokenBalance: shareTokens,
667
+ poolShareToken: shareTokenAddr,
668
+ };
669
+ }
670
+
671
+ private _poolSymbolOrIdToPoolId(poolSymbolOrId: string | number): number {
672
+ if (this.proxyContract == null || this.poolStaticInfos.length == 0) {
673
+ throw Error("no proxy contract or wallet or data initialized. Use createProxyInstance().");
674
+ }
675
+ let poolId: number;
676
+ if (isNaN(Number(poolSymbolOrId))) {
677
+ poolId = PerpetualDataHandler._getPoolIdFromSymbol(poolSymbolOrId as string, this.poolStaticInfos);
678
+ } else {
679
+ poolId = Number(poolSymbolOrId);
680
+ }
681
+ return poolId;
682
+ }
683
+
684
+ /**
685
+ * Gets the maximal order size to open positions (increase size),
686
+ * considering the existing position, state of the perpetual
687
+ * Ignores users wallet balance.
688
+ * @param side BUY or SELL
689
+ * @param positionRisk Current position risk (as seen in positionRisk)
690
+ * @returns Maximal trade size, not signed
691
+ */
692
+ public async maxOrderSizeForTrader(
693
+ side: string,
694
+ positionRisk: MarginAccount,
695
+ overrides?: CallOverrides
696
+ ): Promise<number> {
697
+ let curPosition = side == BUY_SIDE ? positionRisk.positionNotionalBaseCCY : -positionRisk.positionNotionalBaseCCY;
698
+ let perpId = this.getPerpIdFromSymbol(positionRisk.symbol);
699
+ let perpMaxPositionABK = await this.proxyContract!.getMaxSignedOpenTradeSizeForPos(
700
+ perpId,
701
+ floatToABK64x64(curPosition),
702
+ side == BUY_SIDE,
703
+ overrides || {}
704
+ );
705
+ return ABK64x64ToFloat(perpMaxPositionABK.abs());
706
+ }
707
+
708
+ /**
709
+ *
710
+ * @param side BUY_SIDE or SELL_SIDE
711
+ * @param symbol of the form ETH-USD-MATIC.
712
+ * @returns signed maximal position size in base currency
713
+ */
714
+ public async maxSignedPosition(side: string, symbol: string, overrides?: CallOverrides): Promise<number> {
715
+ let perpId = this.getPerpIdFromSymbol(symbol);
716
+ let isBuy = side == BUY_SIDE;
717
+ let maxSignedPos = await this.proxyContract!.getMaxSignedOpenTradeSizeForPos(
718
+ perpId,
719
+ BigNumber.from(0),
720
+ isBuy,
721
+ overrides || {}
722
+ );
723
+ return ABK64x64ToFloat(maxSignedPos);
724
+ }
725
+
726
+ /**
727
+ * Uses the Oracle(s) in the exchange to get the latest price of a given index in a given currency, if a route exists.
728
+ * @param {string} base Index name, e.g. ETH.
729
+ * @param {string} quote Quote currency, e.g. USD.
730
+ * @example
731
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
732
+ * async function main() {
733
+ * console.log(MarketData);
734
+ * // setup
735
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
736
+ * let mktData = new MarketData(config);
737
+ * await mktData.createProxyInstance();
738
+ * // get oracle price
739
+ * let price = await mktData.getOraclePrice("ETH", "USD");
740
+ * console.log(price);
741
+ * }
742
+ * main();
743
+ *
744
+ * @returns {number} Price of index in given currency.
745
+ */
746
+ public async getOraclePrice(base: string, quote: string, overrides?: CallOverrides): Promise<number | undefined> {
747
+ if (!this.proxyContract) {
748
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
749
+ }
750
+ let px = await this.proxyContract.getOraclePrice([toBytes4(base), toBytes4(quote)], overrides || {});
751
+ return px == undefined ? undefined : ABK64x64ToFloat(px);
752
+ }
753
+
754
+ /**
755
+ *
756
+ * @param symbol Symbol of the form ETH-USD-MATIC
757
+ * @param orderId Order Id
758
+ * @param overrides
759
+ * @returns Order status ()
760
+ */
761
+ public async getOrderStatus(symbol: string, orderId: string, overrides?: CallOverrides): Promise<number> {
762
+ if (!this.proxyContract) {
763
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
764
+ }
765
+ const orderBookContract = this.getOrderBookContract(symbol);
766
+ let status = await orderBookContract.getOrderStatus(orderId, overrides || {});
767
+ return status;
768
+ }
769
+
770
+ /**
771
+ * Get the current mark price
772
+ * @param symbol symbol of the form ETH-USD-MATIC
773
+ * @example
774
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
775
+ * async function main() {
776
+ * console.log(MarketData);
777
+ * // setup
778
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
779
+ * let mktData = new MarketData(config);
780
+ * await mktData.createProxyInstance();
781
+ * // get mark price
782
+ * let price = await mktData.getMarkPrice("ETH-USD-MATIC");
783
+ * console.log(price);
784
+ * }
785
+ * main();
786
+ *
787
+ * @returns mark price
788
+ */
789
+ public async getMarkPrice(symbol: string, indexPrices?: [number, number]): Promise<number> {
790
+ if (this.proxyContract == null) {
791
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
792
+ }
793
+ if (indexPrices == undefined) {
794
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
795
+ indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
796
+ }
797
+ return await PerpetualDataHandler._queryPerpetualMarkPrice(
798
+ symbol,
799
+ this.symbolToPerpStaticInfo,
800
+ this.proxyContract,
801
+ indexPrices
802
+ );
803
+ }
804
+
805
+ /**
806
+ * get the current price for a given quantity
807
+ * @param symbol symbol of the form ETH-USD-MATIC
808
+ * @param quantity quantity to be traded, negative if short
809
+ * @example
810
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
811
+ * async function main() {
812
+ * console.log(MarketData);
813
+ * // setup
814
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
815
+ * let mktData = new MarketData(config);
816
+ * await mktData.createProxyInstance();
817
+ * // get perpetual price
818
+ * let price = await mktData.getPerpetualPrice("ETH-USD-MATIC", 1);
819
+ * console.log(price);
820
+ * }
821
+ * main();
822
+ *
823
+ * @returns price (number)
824
+ */
825
+ public async getPerpetualPrice(
826
+ symbol: string,
827
+ quantity: number,
828
+ indexPrices?: [number, number],
829
+ overrides?: CallOverrides
830
+ ): Promise<number> {
831
+ if (this.proxyContract == null) {
832
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
833
+ }
834
+ if (indexPrices == undefined) {
835
+ // fetch from API
836
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
837
+ indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
838
+ }
839
+ return await PerpetualDataHandler._queryPerpetualPrice(
840
+ symbol,
841
+ quantity,
842
+ this.symbolToPerpStaticInfo,
843
+ this.proxyContract,
844
+ indexPrices,
845
+ overrides
846
+ );
847
+ }
848
+
849
+ /**
850
+ * Query recent perpetual state from blockchain
851
+ * @param symbol symbol of the form ETH-USD-MATIC
852
+ * @param indexPrices S2 and S3 prices/isMarketOpen if not provided fetch via REST API
853
+ * @returns PerpetualState reference
854
+ */
855
+ public async getPerpetualState(
856
+ symbol: string,
857
+ indexPriceInfo?: [number, number, boolean, boolean],
858
+ overrides?: CallOverrides
859
+ ): Promise<PerpetualState> {
860
+ if (this.proxyContract == null) {
861
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
862
+ }
863
+ if (indexPriceInfo == undefined) {
864
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
865
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
866
+ }
867
+ let state: PerpetualState = await PerpetualDataHandler._queryPerpetualState(
868
+ symbol,
869
+ this.symbolToPerpStaticInfo,
870
+ this.proxyContract,
871
+ indexPriceInfo,
872
+ overrides
873
+ );
874
+ return state;
875
+ }
876
+
877
+ /**
878
+ * Query perpetual static info.
879
+ * This information is queried once at createProxyInstance-time and remains static after that.
880
+ * @param symbol symbol of the form ETH-USD-MATIC
881
+ * @returns PerpetualStaticInfo copy.
882
+ */
883
+ public getPerpetualStaticInfo(symbol: string): PerpetualStaticInfo {
884
+ let perpInfo = this.symbolToPerpStaticInfo.get(symbol);
885
+ if (perpInfo == undefined) {
886
+ throw Error(`Perpetual with symbol ${symbol} not found. Check symbol or use createProxyInstance().`);
887
+ }
888
+ // return new copy, not a reference
889
+ let res: PerpetualStaticInfo = {
890
+ id: perpInfo.id,
891
+ poolId: perpInfo.poolId,
892
+ limitOrderBookAddr: perpInfo.limitOrderBookAddr,
893
+ initialMarginRate: perpInfo.initialMarginRate,
894
+ maintenanceMarginRate: perpInfo.maintenanceMarginRate,
895
+ collateralCurrencyType: perpInfo.collateralCurrencyType,
896
+ S2Symbol: perpInfo.S2Symbol,
897
+ S3Symbol: perpInfo.S3Symbol,
898
+ lotSizeBC: perpInfo.lotSizeBC,
899
+ referralRebate: perpInfo.referralRebate,
900
+ priceIds: perpInfo.priceIds,
901
+ };
902
+ return res;
903
+ }
904
+
905
+ /**
906
+ * get the current mid-price for a perpetual
907
+ * @param symbol symbol of the form ETH-USD-MATIC
908
+ * @example
909
+ * import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
910
+ * async function main() {
911
+ * console.log(MarketData);
912
+ * // setup
913
+ * const config = PerpetualDataHandler.readSDKConfig("testnet");
914
+ * let mktData = new MarketData(config);
915
+ * await mktData.createProxyInstance();
916
+ * // get perpetual mid price
917
+ * let midPrice = await mktData.getPerpetualMidPrice("ETH-USD-MATIC");
918
+ * console.log(midPrice);
919
+ * }
920
+ * main();
921
+ *
922
+ * @returns {number} price
923
+ */
924
+ public async getPerpetualMidPrice(symbol: string): Promise<number> {
925
+ if (this.proxyContract == null) {
926
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
927
+ }
928
+ return await this.getPerpetualPrice(symbol, 0);
929
+ }
930
+
931
+ /**
932
+ * Query smart contract to get user orders and convert to user friendly order format.
933
+ * @param {string} traderAddr Address of trader.
934
+ * @param {ethers.Contract} orderBookContract Instance of order book.
935
+ * @returns {Order[]} Array of user friendly order struct.
936
+ * @ignore
937
+ */
938
+ protected async openOrdersOnOrderBook(
939
+ traderAddr: string,
940
+ orderBookContract: LimitOrderBook,
941
+ overrides?: CallOverrides
942
+ ): Promise<Order[]> {
943
+ //eliminate empty orders and map to user friendly orders
944
+ let userFriendlyOrders: Order[] = new Array<Order>();
945
+ let haveMoreOrders = true;
946
+ let from = 0;
947
+ const bulkSize = 15;
948
+ while (haveMoreOrders) {
949
+ let orders: IClientOrder.ClientOrderStructOutput[] = await orderBookContract.getOrders(
950
+ traderAddr,
951
+ from,
952
+ bulkSize,
953
+ overrides || {}
954
+ );
955
+ let k = 0;
956
+ while (k < orders.length && orders[k].traderAddr !== ZERO_ADDRESS) {
957
+ userFriendlyOrders.push(PerpetualDataHandler.fromClientOrder(orders[k], this.symbolToPerpStaticInfo));
958
+ k++;
959
+ }
960
+ haveMoreOrders = orders[orders.length - 1].traderAddr !== ZERO_ADDRESS;
961
+ from = from + bulkSize;
962
+ }
963
+ return userFriendlyOrders;
964
+ }
965
+
966
+ /**
967
+ *
968
+ * @param traderAddr Address of the trader
969
+ * @param orderBookContract Instance of order book contract
970
+ * @returns Array of order-id's
971
+ * @ignore
972
+ */
973
+ public static async orderIdsOfTrader(
974
+ traderAddr: string,
975
+ orderBookContract: LimitOrderBook,
976
+ overrides?: CallOverrides
977
+ ): Promise<string[]> {
978
+ let digestsRaw: string[] = await orderBookContract.limitDigestsOfTrader(traderAddr, 0, 15, overrides || {});
979
+ let k: number = 0;
980
+ let digests: string[] = [];
981
+ while (k < digestsRaw.length && BigNumber.from(digestsRaw[k]).gt(0)) {
982
+ digests.push(digestsRaw[k]);
983
+ k++;
984
+ }
985
+ return digests;
986
+ }
987
+
988
+ /**
989
+ * Query the available margin conditional on the given (or current) index prices
990
+ * Result is in collateral currency
991
+ * @param traderAddr address of the trader
992
+ * @param symbol perpetual symbol of the form BTC-USD-MATIC
993
+ * @param indexPrices optional index prices, will otherwise fetch from REST API
994
+ * @returns available margin in collateral currency
995
+ */
996
+ public async getAvailableMargin(
997
+ traderAddr: string,
998
+ symbol: string,
999
+ indexPrices?: [number, number],
1000
+ overrides?: CallOverrides
1001
+ ): Promise<number> {
1002
+ if (!this.proxyContract) {
1003
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
1004
+ }
1005
+
1006
+ if (indexPrices == undefined) {
1007
+ // fetch from API
1008
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
1009
+ indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
1010
+ }
1011
+ let perpID = PerpetualDataHandler.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
1012
+ let traderState = await this.proxyContract.getTraderState(
1013
+ perpID,
1014
+ traderAddr,
1015
+ indexPrices.map((x) => floatToABK64x64(x)) as [BigNumber, BigNumber],
1016
+ overrides || {}
1017
+ );
1018
+ const idx_availableMargin = 1;
1019
+ let mgn = ABK64x64ToFloat(traderState[idx_availableMargin]);
1020
+ return mgn;
1021
+ }
1022
+
1023
+ /**
1024
+ * Calculate a type of exchange loyality score based on trader volume
1025
+ * @param traderAddr address of the trader
1026
+ * @param brokerAddr address of the trader's broker or undefined
1027
+ * @returns a loyality score (4 worst, 1 best)
1028
+ */
1029
+ public async getTraderLoyalityScore(
1030
+ traderAddr: string,
1031
+ brokerAddr?: string,
1032
+ overrides?: CallOverrides
1033
+ ): Promise<number> {
1034
+ if (this.proxyContract == null) {
1035
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
1036
+ }
1037
+ // loop over all pools and query volumes
1038
+ let brokerProm: Array<Promise<BigNumber>> = [];
1039
+ let traderProm: Array<Promise<BigNumber>> = [];
1040
+ for (let k = 0; k < this.poolStaticInfos.length; k++) {
1041
+ if (brokerAddr != "" && brokerAddr != undefined) {
1042
+ let brkrVol = this.proxyContract.getCurrentBrokerVolume(
1043
+ this.poolStaticInfos[k].poolId,
1044
+ brokerAddr,
1045
+ overrides || {}
1046
+ );
1047
+ brokerProm.push(brkrVol);
1048
+ }
1049
+ let trdrVol = this.proxyContract.getCurrentTraderVolume(
1050
+ this.poolStaticInfos[k].poolId,
1051
+ traderAddr,
1052
+ overrides || {}
1053
+ );
1054
+ traderProm.push(trdrVol);
1055
+ }
1056
+ // sum
1057
+ let totalBrokerVolume = 0;
1058
+ let totalTraderVolume = 0;
1059
+ let brkrVol = await Promise.all(brokerProm);
1060
+ let trdrVol = await Promise.all(traderProm);
1061
+ for (let k = 0; k < this.poolStaticInfos.length; k++) {
1062
+ if (brokerAddr != "" && brokerAddr != undefined) {
1063
+ totalBrokerVolume += ABK64x64ToFloat(brkrVol[k]);
1064
+ }
1065
+ totalTraderVolume += ABK64x64ToFloat(trdrVol[k]);
1066
+ }
1067
+ const volumeCap = 500_000;
1068
+ let score = totalBrokerVolume == 0 ? totalTraderVolume / volumeCap : totalBrokerVolume;
1069
+ // 5 different equally spaced categories: (4 is best, 1 worst)
1070
+ let rank4 = 1 + Math.floor(Math.min(score, 1 - 1e-15) * 4);
1071
+ // desired ranking starts at 4 (worst) and ends at 1 (best)
1072
+ return 5 - rank4;
1073
+ }
1074
+
1075
+ /**
1076
+ * Get all off-chain prices
1077
+ * @param _symbolToPerpStaticInfo mapping: PerpetualStaticInfo for each perpetual
1078
+ * @param _priceFeedGetter priceFeed class from which we can get offchain price data
1079
+ * @returns mapping of symbol-pair (e.g. BTC-USD) to price/isMarketClosed
1080
+ */
1081
+ private static async _getAllIndexPrices(
1082
+ _symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
1083
+ _priceFeedGetter: PriceFeeds
1084
+ ): Promise<Map<string, [number, boolean]>> {
1085
+ // get all prices from off-chain price-sources
1086
+ let allSym = new Set<string>();
1087
+ for (let perpSymbol of _symbolToPerpStaticInfo.keys()) {
1088
+ let sInfo: PerpetualStaticInfo | undefined = _symbolToPerpStaticInfo.get(perpSymbol);
1089
+ allSym.add(sInfo!.S2Symbol);
1090
+ if (sInfo!.S3Symbol != "") {
1091
+ allSym.add(sInfo!.S3Symbol);
1092
+ }
1093
+ }
1094
+ let allSymArr = Array.from(allSym.values());
1095
+ let idxPriceMap: Map<string, [number, boolean]> = await _priceFeedGetter.fetchPrices(allSymArr);
1096
+ return idxPriceMap;
1097
+ }
1098
+
1099
+ /**
1100
+ * Get market open/closed status
1101
+ * @param symbol Perpetual symbol of the form ETH-USD-MATIC
1102
+ * @returns True if the market is closed
1103
+ */
1104
+ public async isMarketClosed(symbol: string): Promise<boolean> {
1105
+ if (this.proxyContract == null) {
1106
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
1107
+ }
1108
+ return await MarketData._isMarketClosed(symbol, this.symbolToPerpStaticInfo, this.priceFeedGetter);
1109
+ }
1110
+
1111
+ private static async _isMarketClosed(
1112
+ symbol: string,
1113
+ _symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
1114
+ _priceFeedGetter: PriceFeeds
1115
+ ): Promise<boolean> {
1116
+ const sInfo: PerpetualStaticInfo | undefined = _symbolToPerpStaticInfo.get(symbol);
1117
+ let priceSymbols: string[] = [];
1118
+ if (sInfo?.S2Symbol != undefined && sInfo.S2Symbol != "") {
1119
+ priceSymbols.push(sInfo.S2Symbol);
1120
+ }
1121
+ if (sInfo?.S3Symbol != undefined && sInfo.S3Symbol != "") {
1122
+ priceSymbols.push(sInfo.S3Symbol);
1123
+ }
1124
+ if (priceSymbols.length == 0) {
1125
+ throw new Error("symbol not found");
1126
+ }
1127
+ const priceInfos = await _priceFeedGetter.fetchPrices(priceSymbols);
1128
+ return [...priceInfos.values()].some((p) => p[1]);
1129
+ }
1130
+
1131
+ /**
1132
+ * Collect all mid-prices
1133
+ * @param _proxyContract contract instance
1134
+ * @param _nestedPerpetualIDs contains all perpetual ids for each pool
1135
+ * @param _symbolToPerpStaticInfo maps symbol to static info
1136
+ * @param _perpetualIdToSymbol maps perpetual id to symbol of the form BTC-USD-MATIC
1137
+ * @param _idxPriceMap symbol to price/market closed
1138
+ * @returns perpetual symbol to mid-prices mapping
1139
+ */
1140
+ private static async _queryMidPrices(
1141
+ _proxyContract: Contract,
1142
+ _nestedPerpetualIDs: Array<Array<number>>,
1143
+ _symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
1144
+ _perpetualIdToSymbol: Map<number, string>,
1145
+ _idxPriceMap: Map<string, [number, boolean]>,
1146
+ overrides?: CallOverrides
1147
+ ): Promise<Map<string, number>> {
1148
+ // what is the maximal number of queries at once?
1149
+ const chunkSize = 10;
1150
+ let perpetualIDChunks: Array<Array<number>> = PerpetualDataHandler.nestedIDsToChunks(
1151
+ chunkSize,
1152
+ _nestedPerpetualIDs
1153
+ );
1154
+
1155
+ let midPriceMap = new Map<string, number>();
1156
+ for (let k = 0; k < perpetualIDChunks.length; k++) {
1157
+ let indexPrices: BigNumber[] = [];
1158
+ // collect/order all index prices
1159
+ for (let j = 0; j < perpetualIDChunks[k].length; j++) {
1160
+ let id = perpetualIDChunks[k][j];
1161
+ let symbol3s = _perpetualIdToSymbol.get(id);
1162
+ let info = _symbolToPerpStaticInfo.get(symbol3s!);
1163
+ let S2 = floatToABK64x64(_idxPriceMap.get(info!.S2Symbol)![0]);
1164
+ let S3 = BigNumber.from(0);
1165
+ if (info!.S3Symbol != "") {
1166
+ S3 = floatToABK64x64(_idxPriceMap.get(info!.S3Symbol)![0]);
1167
+ }
1168
+ indexPrices.push(S2);
1169
+ indexPrices.push(S3);
1170
+ }
1171
+ let fMidPrice = await _proxyContract.queryMidPrices(perpetualIDChunks[k], indexPrices, overrides || {});
1172
+ for (let j = 0; j < fMidPrice.length; j++) {
1173
+ let id = perpetualIDChunks[k][j];
1174
+ let symbol3s = _perpetualIdToSymbol.get(id);
1175
+ midPriceMap.set(symbol3s!, ABK64x64ToFloat(fMidPrice[j]));
1176
+ }
1177
+ }
1178
+ return midPriceMap;
1179
+ }
1180
+
1181
+ private static async _queryPoolStates(
1182
+ _proxyContract: Contract,
1183
+ _poolStaticInfos: PoolStaticInfo[],
1184
+ _numPools: number,
1185
+ overrides?: CallOverrides
1186
+ ): Promise<Array<PoolState>> {
1187
+ const chunkSize = 5;
1188
+ let iFrom = 1;
1189
+ let poolStates: Array<PoolState> = [];
1190
+ while (iFrom <= _numPools) {
1191
+ let pools = await _proxyContract.getLiquidityPools(iFrom, iFrom + chunkSize, overrides || {});
1192
+ for (let k = 0; k < pools.length; k++) {
1193
+ let poolSymbol = _poolStaticInfos[iFrom + k - 1].poolMarginSymbol;
1194
+ let poolState: PoolState = {
1195
+ isRunning: pools[k].isRunning,
1196
+ poolSymbol: poolSymbol,
1197
+ marginTokenAddr: pools[k].marginTokenAddress,
1198
+ poolShareTokenAddr: pools[k].shareTokenAddress,
1199
+ defaultFundCashCC: ABK64x64ToFloat(pools[k].fDefaultFundCashCC),
1200
+ pnlParticipantCashCC: ABK64x64ToFloat(pools[k].fPnLparticipantsCashCC),
1201
+ totalAMMFundCashCC: ABK64x64ToFloat(pools[k].fAMMFundCashCC),
1202
+ totalTargetAMMFundSizeCC: ABK64x64ToFloat(pools[k].fTargetAMMFundSize),
1203
+ brokerCollateralLotSize: ABK64x64ToFloat(pools[k].fBrokerCollateralLotSize),
1204
+ perpetuals: [],
1205
+ };
1206
+ poolStates.push(poolState);
1207
+ }
1208
+ iFrom = iFrom + chunkSize + 1;
1209
+ }
1210
+ return poolStates;
1211
+ }
1212
+
1213
+ private static async _queryPerpetualStates(
1214
+ _proxyContract: Contract,
1215
+ _nestedPerpetualIDs: Array<Array<number>>,
1216
+ _symbolList: Map<string, string>,
1217
+ overrides?: CallOverrides
1218
+ ) {
1219
+ // what is the maximal number of queries at once?
1220
+ const chunkSize = 10;
1221
+ let perpetualIDChunks: Array<Array<number>> = PerpetualDataHandler.nestedIDsToChunks(
1222
+ chunkSize,
1223
+ _nestedPerpetualIDs
1224
+ );
1225
+ let perpStateInfos = new Array<PerpetualState>();
1226
+ for (let k = 0; k < perpetualIDChunks.length; k++) {
1227
+ let perps = await _proxyContract.getPerpetuals(perpetualIDChunks[k], overrides || {});
1228
+ for (let j = 0; j < perps.length; j++) {
1229
+ let PerpetualState: PerpetualState = {
1230
+ id: perps[j].id,
1231
+ state: PERP_STATE_STR[perps[j].state],
1232
+ baseCurrency: contractSymbolToSymbol(perps[j].S2BaseCCY, _symbolList)!,
1233
+ quoteCurrency: contractSymbolToSymbol(perps[j].S2QuoteCCY, _symbolList)!,
1234
+ indexPrice: 0, //fill later
1235
+ collToQuoteIndexPrice: 0, //fill later
1236
+ markPrice: ABK64x64ToFloat(perps[j].currentMarkPremiumRate.fPrice), // fill later: indexS2 * (1 + markPremiumRate),
1237
+ midPrice: 0, // fill later
1238
+ currentFundingRateBps: 1e4 * ABK64x64ToFloat(perps[j].fCurrentFundingRate),
1239
+ openInterestBC: ABK64x64ToFloat(perps[j].fOpenInterest),
1240
+ isMarketClosed: false, //fill later
1241
+ };
1242
+ perpStateInfos.push(PerpetualState);
1243
+ }
1244
+ }
1245
+ return perpStateInfos;
1246
+ }
1247
+
1248
+ public static async _exchangeInfo(
1249
+ _proxyContract: Contract,
1250
+ _poolStaticInfos: Array<PoolStaticInfo>,
1251
+ _symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
1252
+ _perpetualIdToSymbol: Map<number, string>,
1253
+ _nestedPerpetualIDs: Array<Array<number>>,
1254
+ _symbolList: Map<string, string>,
1255
+ _priceFeedGetter: PriceFeeds,
1256
+ overrides?: CallOverrides
1257
+ ): Promise<ExchangeInfo> {
1258
+ // get the factory address (shared among all pools)
1259
+ let factory = _poolStaticInfos[0].oracleFactoryAddr;
1260
+ let info: ExchangeInfo = { pools: [], oracleFactoryAddr: factory, proxyAddr: _proxyContract.address };
1261
+ const numPools = _nestedPerpetualIDs.length;
1262
+
1263
+ // get all prices from off-chain price-sources
1264
+ let idxPriceMap = await MarketData._getAllIndexPrices(_symbolToPerpStaticInfo, _priceFeedGetter);
1265
+ // query mid-prices from on-chain conditional on the off-chain prices
1266
+ let midPriceMap: Map<string, number> = await MarketData._queryMidPrices(
1267
+ _proxyContract,
1268
+ _nestedPerpetualIDs,
1269
+ _symbolToPerpStaticInfo,
1270
+ _perpetualIdToSymbol,
1271
+ idxPriceMap,
1272
+ overrides
1273
+ );
1274
+ let poolStateInfos = await MarketData._queryPoolStates(_proxyContract, _poolStaticInfos, numPools, overrides);
1275
+ let perpStateInfos = await MarketData._queryPerpetualStates(
1276
+ _proxyContract,
1277
+ _nestedPerpetualIDs,
1278
+ _symbolList,
1279
+ overrides
1280
+ );
1281
+ // put together all info
1282
+ for (let k = 0; k < perpStateInfos.length; k++) {
1283
+ const perp = perpStateInfos[k];
1284
+ let symbol3s = _perpetualIdToSymbol.get(perp.id);
1285
+ let info = _symbolToPerpStaticInfo.get(symbol3s!);
1286
+ const idxPriceS2Pair = idxPriceMap.get(info!.S2Symbol);
1287
+ let idxPriceS3Pair: [number, boolean] = [0, false];
1288
+ perp.isMarketClosed = idxPriceS2Pair![1];
1289
+ if (info!.S3Symbol != "") {
1290
+ idxPriceS3Pair = idxPriceMap.get(info!.S3Symbol)!;
1291
+ perp.isMarketClosed = perp.isMarketClosed || idxPriceS3Pair![1];
1292
+ }
1293
+ perp.indexPrice = idxPriceS2Pair![0];
1294
+ perp.markPrice = idxPriceS2Pair![0] * (1 + perp.markPrice); // currently filled with mark premium rate
1295
+ let indexS3 = 1;
1296
+ if (info!.collateralCurrencyType == COLLATERAL_CURRENCY_BASE) {
1297
+ indexS3 = idxPriceS2Pair![0];
1298
+ } else if (info!.collateralCurrencyType == COLLATERAL_CURRENCY_QUANTO) {
1299
+ indexS3 = idxPriceS3Pair[0];
1300
+ }
1301
+ perp.collToQuoteIndexPrice = indexS3;
1302
+ perp.midPrice = midPriceMap.get(symbol3s!)!;
1303
+ // which pool?
1304
+ const poolId = info!.poolId;
1305
+ poolStateInfos[poolId - 1].perpetuals.push(perp);
1306
+ }
1307
+ info.pools = poolStateInfos;
1308
+ return info;
1309
+ }
1310
+ }