@d8x/perpetuals-sdk 0.0.1

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/abi/ERC20.json +288 -0
  4. package/abi/IPerpetualManager.json +4674 -0
  5. package/abi/LimitOrderBook.json +865 -0
  6. package/abi/LimitOrderBookFactory.json +166 -0
  7. package/config/defaultConfig.json +9 -0
  8. package/config/oldConfig.json +9 -0
  9. package/dist/accountTrade.d.ts +54 -0
  10. package/dist/accountTrade.js +164 -0
  11. package/dist/brokerTool.d.ts +41 -0
  12. package/dist/brokerTool.js +129 -0
  13. package/dist/d8XMath.d.ts +71 -0
  14. package/dist/d8XMath.js +162 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.js +49 -0
  17. package/dist/liquiditatorTool.d.ts +14 -0
  18. package/dist/liquiditatorTool.js +21 -0
  19. package/dist/liquidityProviderTool.d.ts +39 -0
  20. package/dist/liquidityProviderTool.js +100 -0
  21. package/dist/marketData.d.ts +39 -0
  22. package/dist/marketData.js +160 -0
  23. package/dist/nodeSDKTypes.d.ts +130 -0
  24. package/dist/nodeSDKTypes.js +52 -0
  25. package/dist/orderReferrerTool.d.ts +14 -0
  26. package/dist/orderReferrerTool.js +21 -0
  27. package/dist/perpetualDataHandler.d.ts +85 -0
  28. package/dist/perpetualDataHandler.js +474 -0
  29. package/dist/utils.d.ts +37 -0
  30. package/dist/utils.js +84 -0
  31. package/dist/writeAccessHandler.d.ts +36 -0
  32. package/dist/writeAccessHandler.js +95 -0
  33. package/module.d.ts +1 -0
  34. package/package.json +63 -0
  35. package/src/accountTrade.ts +217 -0
  36. package/src/brokerTool.ts +155 -0
  37. package/src/d8XMath.ts +176 -0
  38. package/src/index.ts +32 -0
  39. package/src/liquiditatorTool.ts +21 -0
  40. package/src/liquidityProviderTool.ts +100 -0
  41. package/src/marketData.ts +149 -0
  42. package/src/nodeSDKTypes.ts +158 -0
  43. package/src/orderReferrerTool.ts +17 -0
  44. package/src/perpetualDataHandler.ts +549 -0
  45. package/src/utils.ts +83 -0
  46. package/src/writeAccessHandler.ts +83 -0
@@ -0,0 +1,549 @@
1
+ import { ethers, BigNumber } from "ethers";
2
+ import {
3
+ NodeSDKConfig,
4
+ MAX_64x64,
5
+ Order,
6
+ SmartContractOrder,
7
+ CollaterlCCY,
8
+ PerpetualStaticInfo,
9
+ COLLATERAL_CURRENCY_BASE,
10
+ COLLATERAL_CURRENCY_QUOTE,
11
+ BUY_SIDE,
12
+ SELL_SIDE,
13
+ CLOSED_SIDE,
14
+ ORDER_MAX_DURATION_SEC,
15
+ ZERO_ADDRESS,
16
+ ORDER_TYPE_LIMIT,
17
+ ORDER_TYPE_MARKET,
18
+ ORDER_TYPE_STOP_MARKET,
19
+ ORDER_TYPE_STOP_LIMIT,
20
+ MASK_LIMIT_ORDER,
21
+ MASK_CLOSE_ONLY,
22
+ MASK_KEEP_POS_LEVERAGE,
23
+ MASK_MARKET_ORDER,
24
+ MASK_STOP_ORDER,
25
+ MarginAccount,
26
+ PoolStaticInfo,
27
+ DEFAULT_CONFIG_MAINNET_NAME,
28
+ DEFAULT_CONFIG_MAINNET,
29
+ DEFAULT_CONFIG_TESTNET_NAME,
30
+ DEFAULT_CONFIG_TESTNET,
31
+ } from "./nodeSDKTypes";
32
+ import { fromBytes4HexString, to4Chars, combineFlags, containsFlag } from "./utils";
33
+ import {
34
+ ABK64x64ToFloat,
35
+ floatToABK64x64,
36
+ div64x64,
37
+ calculateLiquidationPriceCollateralQuanto,
38
+ calculateLiquidationPriceCollateralBase,
39
+ calculateLiquidationPriceCollateralQuote,
40
+ } from "./d8XMath";
41
+ import { Config } from "jest";
42
+
43
+ /**
44
+ * Parent class for AccountTrade and MarketData that handles
45
+ * common data and chain operations
46
+ */
47
+ export default class PerpetualDataHandler {
48
+ //map symbol of the form ETH-USD-MATIC into perpetual ID and other static info
49
+ //this is initialized in the createProxyInstance function
50
+ protected symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>;
51
+ protected poolStaticInfos: Array<PoolStaticInfo>;
52
+ //map margin token of the form MATIC or ETH or USDC into
53
+ //the address of the margin token
54
+ protected symbolToTokenAddrMap: Map<string, string>;
55
+
56
+ protected proxyContract: ethers.Contract | null = null;
57
+ protected proxyABI: ethers.ContractInterface;
58
+ protected proxyAddr: string;
59
+ // limit order book
60
+ protected lobFactoryContract: ethers.Contract | null = null;
61
+ protected lobFactoryABI: ethers.ContractInterface;
62
+ protected lobFactoryAddr: string;
63
+ protected lobABI: ethers.ContractInterface;
64
+ protected nodeURL: string;
65
+ protected provider: ethers.providers.JsonRpcProvider | null = null;
66
+
67
+ private signerOrProvider: ethers.Signer | ethers.providers.Provider | null = null;
68
+
69
+ // pools are numbered consecutively starting at 1
70
+ // nestedPerpetualIDs contains an array for each pool
71
+ // each pool-array contains perpetual ids
72
+ protected nestedPerpetualIDs: number[][];
73
+
74
+ public constructor(config: NodeSDKConfig) {
75
+ this.symbolToPerpStaticInfo = new Map<string, PerpetualStaticInfo>();
76
+ this.poolStaticInfos = new Array<PoolStaticInfo>();
77
+ this.symbolToTokenAddrMap = new Map<string, string>();
78
+ this.nestedPerpetualIDs = new Array<Array<number>>();
79
+ this.proxyAddr = config.proxyAddr;
80
+ this.lobFactoryAddr = config.limitOrderBookFactoryAddr;
81
+ this.nodeURL = config.nodeURL;
82
+ this.proxyABI = require(config.proxyABILocation);
83
+ this.lobFactoryABI = require(config.limitOrderBookFactoryABILocation);
84
+ this.lobABI = require(config.limitOrderBookABILocation);
85
+ }
86
+
87
+ protected async initContractsAndData(signerOrProvider: ethers.Signer | ethers.providers.Provider) {
88
+ this.signerOrProvider = signerOrProvider;
89
+ this.proxyContract = new ethers.Contract(this.proxyAddr, this.proxyABI, signerOrProvider);
90
+ this.lobFactoryContract = new ethers.Contract(this.lobFactoryAddr, this.lobFactoryABI, signerOrProvider);
91
+ await this._fillSymbolMaps(this.proxyContract);
92
+ }
93
+
94
+ /**
95
+ * Returns the order-book contract for the symbol if found or fails
96
+ * @param symbol symbol of the form ETH-USD-MATIC
97
+ * @returns order book contract for the perpetual
98
+ */
99
+ public getOrderBookContract(symbol: string): ethers.Contract {
100
+ let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
101
+ let orderBookAddr = this.symbolToPerpStaticInfo.get(cleanSymbol)?.limitOrderBookAddr;
102
+ if (orderBookAddr == "" || orderBookAddr == undefined || this.signerOrProvider == null) {
103
+ throw Error(`no limit order book found for ${symbol} or no signer`);
104
+ }
105
+ let lobContract = new ethers.Contract(orderBookAddr, this.lobABI, this.signerOrProvider);
106
+ return lobContract;
107
+ }
108
+
109
+ /**
110
+ * Called when initializing. This function fills this.symbolToTokenAddrMap,
111
+ * and this.nestedPerpetualIDs and this.symbolToPerpStaticInfo
112
+ *
113
+ */
114
+ protected async _fillSymbolMaps(proxyContract: ethers.Contract) {
115
+ if (proxyContract == null || this.lobFactoryContract == null) {
116
+ throw Error("proxy or limit order book not defined");
117
+ }
118
+ this.nestedPerpetualIDs = await PerpetualDataHandler.getNestedPerpetualIds(proxyContract);
119
+ for (let j = 0; j < this.nestedPerpetualIDs.length; j++) {
120
+ let pool = await proxyContract.getLiquidityPool(j + 1);
121
+ let poolMarginTokenAddr = pool.marginTokenAddress;
122
+ let perpetualIDs = this.nestedPerpetualIDs[j];
123
+ let poolCCY: string | undefined = undefined;
124
+ let currentSymbols: string[] = [];
125
+ let currentSymbolsS3: string[] = [];
126
+ let currentLimitOrderBookAddr: string[] = [];
127
+ let ccy: CollaterlCCY[] = [];
128
+ let mgnRate: number[] = [];
129
+
130
+ for (let k = 0; k < perpetualIDs.length; k++) {
131
+ let perp = await proxyContract.getPerpetual(perpetualIDs[k]);
132
+ let base = fromBytes4HexString(perp.S2BaseCCY);
133
+ let quote = fromBytes4HexString(perp.S2QuoteCCY);
134
+ let base3 = fromBytes4HexString(perp.S3BaseCCY);
135
+ let quote3 = fromBytes4HexString(perp.S3QuoteCCY);
136
+ currentSymbols.push(base + "-" + quote);
137
+ currentSymbolsS3.push(base3 + "-" + quote3);
138
+ mgnRate.push(ABK64x64ToFloat(perp.fMaintenanceMarginRate));
139
+ // try to find a limit order book
140
+ let lobAddr = await this.lobFactoryContract.getOrderBookAddress(perpetualIDs[k]);
141
+ currentLimitOrderBookAddr.push(lobAddr);
142
+ // we find out the pool currency by looking at all perpetuals
143
+ // unless for quanto perpetuals, we know the pool currency
144
+ // from the perpetual. This fails if we have a pool with only
145
+ // quanto perpetuals
146
+ if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_BASE) {
147
+ poolCCY = base;
148
+ ccy.push(CollaterlCCY.BASE);
149
+ } else if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_QUOTE) {
150
+ poolCCY = quote;
151
+ ccy.push(CollaterlCCY.QUOTE);
152
+ } else {
153
+ ccy.push(CollaterlCCY.QUANTO);
154
+ }
155
+ }
156
+ if (perpetualIDs.length == 0) {
157
+ continue;
158
+ }
159
+ if (poolCCY == undefined) {
160
+ throw Error("Pool only has quanto perps, unable to determine collateral currency");
161
+ }
162
+ let oracleFactoryAddr = await proxyContract.getOracleFactory();
163
+ let info: PoolStaticInfo = {
164
+ poolId: j + 1,
165
+ poolMarginSymbol: poolCCY,
166
+ poolMarginTokenAddr: poolMarginTokenAddr,
167
+ shareTokenAddr: pool.shareTokenAddress,
168
+ oracleFactoryAddr: oracleFactoryAddr,
169
+ };
170
+ this.poolStaticInfos.push(info);
171
+ let currentSymbols3 = currentSymbols.map((x) => x + "-" + poolCCY);
172
+ // push into map
173
+ for (let k = 0; k < perpetualIDs.length; k++) {
174
+ this.symbolToPerpStaticInfo.set(currentSymbols3[k], {
175
+ id: perpetualIDs[k],
176
+ limitOrderBookAddr: currentLimitOrderBookAddr[k],
177
+ maintenanceMarginRate: mgnRate[k],
178
+ collateralCurrencyType: ccy[k],
179
+ S2Symbol: currentSymbols[k],
180
+ S3Symbol: currentSymbolsS3[k],
181
+ });
182
+ }
183
+ // push margin token address into map
184
+ this.symbolToTokenAddrMap.set(poolCCY, poolMarginTokenAddr);
185
+ }
186
+ }
187
+
188
+ public getSymbolFromPoolId(poolId: number): string {
189
+ return PerpetualDataHandler._getSymbolFromPoolId(poolId, this.poolStaticInfos);
190
+ }
191
+
192
+ public getPoolIdFromSymbol(symbol: string): number {
193
+ return PerpetualDataHandler._getPoolIdFromSymbol(symbol, this.poolStaticInfos);
194
+ }
195
+
196
+ protected static _getSymbolFromPoolId(poolId: number, staticInfos: PoolStaticInfo[]): string {
197
+ let idx = poolId - 1;
198
+ return staticInfos[idx].poolMarginSymbol;
199
+ }
200
+
201
+ protected static _getPoolIdFromSymbol(symbol: string, staticInfos: PoolStaticInfo[]): number {
202
+ let symbols = symbol.split("-");
203
+ //in case user provided ETH-USD-MATIC instead of MATIC; or similar
204
+ if (symbols.length == 3) {
205
+ symbol = symbols[2];
206
+ }
207
+ let cleanSymbol = to4Chars(symbol);
208
+ cleanSymbol = cleanSymbol.replace(/\0/g, "");
209
+ let j = 0;
210
+ while (j < staticInfos.length && staticInfos[j].poolMarginSymbol != cleanSymbol) {
211
+ j++;
212
+ }
213
+ if (j == staticInfos.length) {
214
+ throw new Error(`no pool found for symbol ${symbol}`);
215
+ }
216
+ return j + 1;
217
+ }
218
+
219
+ public static async getNestedPerpetualIds(_proxyContract: ethers.Contract): Promise<number[][]> {
220
+ let poolCount = await _proxyContract.getPoolCount();
221
+ let poolIds: number[][] = new Array(poolCount);
222
+ for (let i = 1; i < poolCount + 1; i++) {
223
+ let perpetualCount = await _proxyContract.getPerpetualCountInPool(i);
224
+ poolIds[i - 1] = new Array(perpetualCount);
225
+ for (let j = 0; j < perpetualCount; j++) {
226
+ let id = await _proxyContract.getPerpetualId(i, j);
227
+ poolIds[i - 1][j] = id;
228
+ }
229
+ }
230
+ return poolIds;
231
+ }
232
+
233
+ public static async getMarginAccount(
234
+ traderAddr: string,
235
+ symbol: string,
236
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
237
+ _proxyContract: ethers.Contract
238
+ ): Promise<MarginAccount> {
239
+ let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
240
+ let perpId = PerpetualDataHandler.symbolToPerpetualId(cleanSymbol, symbolToPerpStaticInfo);
241
+ const idx_cash = 3;
242
+ const idx_notional = 4;
243
+ const idx_locked_in = 5;
244
+ const idx_mark_price = 8;
245
+ const idx_lvg = 7;
246
+ const idx_s3 = 9;
247
+ let traderState = await _proxyContract.getTraderState(perpId, traderAddr);
248
+ let isEmpty = traderState[idx_notional] == 0;
249
+ let cash = ABK64x64ToFloat(traderState[idx_cash]);
250
+ let S2Liq = 0,
251
+ S3Liq = 0,
252
+ tau = Infinity,
253
+ pnl = 0,
254
+ unpaidFundingCC = 0,
255
+ fLockedIn = BigNumber.from(0),
256
+ side = CLOSED_SIDE,
257
+ entryPrice = 0;
258
+ if (!isEmpty) {
259
+ [S2Liq, S3Liq, tau, pnl, unpaidFundingCC] = PerpetualDataHandler._calculateLiquidationPrice(
260
+ cleanSymbol,
261
+ traderState,
262
+ symbolToPerpStaticInfo
263
+ );
264
+ fLockedIn = traderState[idx_locked_in];
265
+ side = traderState[idx_locked_in] > 0 ? BUY_SIDE : SELL_SIDE;
266
+ entryPrice = ABK64x64ToFloat(div64x64(fLockedIn, traderState[idx_notional]));
267
+ }
268
+ let mgn: MarginAccount = {
269
+ symbol: symbol,
270
+ positionNotionalBaseCCY: isEmpty ? 0 : ABK64x64ToFloat(traderState[idx_notional]),
271
+ side: isEmpty ? CLOSED_SIDE : side,
272
+ entryPrice: isEmpty ? 0 : entryPrice,
273
+ leverage: isEmpty ? 0 : ABK64x64ToFloat(traderState[idx_lvg]),
274
+ markPrice: ABK64x64ToFloat(traderState[idx_mark_price].abs()),
275
+ unrealizedPnlQuoteCCY: isEmpty ? 0 : pnl,
276
+ unrealizedFundingCollateralCCY: isEmpty ? 0 : unpaidFundingCC,
277
+ collateralCC: cash,
278
+ liquidationLvg: isEmpty ? 0 : 1 / tau,
279
+ liquidationPrice: isEmpty ? [0, 0] : [S2Liq, S3Liq],
280
+ collToQuoteConversion: ABK64x64ToFloat(traderState[idx_s3]),
281
+ };
282
+ return mgn;
283
+ }
284
+
285
+ /**
286
+ * Liquidation price
287
+ * @param cleanSymbol symbol after calling symbolToBytes4Symbol
288
+ * @param traderState BigInt array according to smart contract
289
+ * @param symbolToPerpStaticInfo mapping symbol->PerpStaticInfo
290
+ * @returns liquidation mark-price, corresponding collateral/quote conversion
291
+ */
292
+ protected static _calculateLiquidationPrice(
293
+ cleanSymbol: string,
294
+ traderState: BigNumber[],
295
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
296
+ ): [number, number, number, number, number] {
297
+ const idx_availableCashCC = 2;
298
+ const idx_cash = 3;
299
+ const idx_notional = 4;
300
+ const idx_locked_in = 5;
301
+ const idx_mark_price = 8;
302
+ const idx_s3 = 9;
303
+ const idx_s2 = 10;
304
+ let S2Liq: number;
305
+ let S3Liq: number = ABK64x64ToFloat(traderState[idx_s3]);
306
+ let perpInfo: PerpetualStaticInfo | undefined = symbolToPerpStaticInfo.get(cleanSymbol);
307
+ if (perpInfo == undefined) {
308
+ throw new Error(`no info for perpetual ${cleanSymbol}`);
309
+ }
310
+ let tau = perpInfo.maintenanceMarginRate;
311
+ let lockedInValueQC = ABK64x64ToFloat(traderState[idx_locked_in]);
312
+ let position = ABK64x64ToFloat(traderState[idx_notional]);
313
+ let cashCC = ABK64x64ToFloat(traderState[idx_availableCashCC]);
314
+ let Sm = ABK64x64ToFloat(traderState[idx_mark_price]);
315
+ let unpaidFundingCC = ABK64x64ToFloat(traderState[idx_availableCashCC].sub(traderState[idx_cash]));
316
+ let unpaidFunding = unpaidFundingCC;
317
+
318
+ if (perpInfo.collateralCurrencyType == CollaterlCCY.BASE) {
319
+ S2Liq = calculateLiquidationPriceCollateralBase(lockedInValueQC, position, cashCC, tau);
320
+ S3Liq = S2Liq;
321
+ unpaidFunding = unpaidFunding / ABK64x64ToFloat(traderState[idx_s2]);
322
+ } else if (perpInfo.collateralCurrencyType == CollaterlCCY.QUANTO) {
323
+ let S3 = S3Liq;
324
+ S3Liq = S3;
325
+ S2Liq = calculateLiquidationPriceCollateralQuanto(lockedInValueQC, position, cashCC, tau, S3, Sm);
326
+ unpaidFunding = unpaidFunding / S3;
327
+ } else {
328
+ S2Liq = calculateLiquidationPriceCollateralQuote(lockedInValueQC, position, cashCC, tau);
329
+ }
330
+ let pnl = position * Sm - lockedInValueQC - unpaidFunding;
331
+ return [S2Liq, S3Liq, tau, pnl, unpaidFundingCC];
332
+ }
333
+
334
+ /**
335
+ * Finds the perpetual id for a symbol of the form
336
+ * <base>-<quote>-<collateral>. The function first converts the
337
+ * token names into bytes4 representation
338
+ * @param symbol symbol (e.g., BTC-USD-MATIC)
339
+ * @param symbolToPerpStaticInfo map that contains the bytes4-symbol to PerpetualStaticInfo
340
+ * including id mapping
341
+ * @returns perpetual id or it fails
342
+ */
343
+ protected static symbolToPerpetualId(
344
+ symbol: string,
345
+ symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>
346
+ ): number {
347
+ let cleanSymbol = PerpetualDataHandler.symbolToBytes4Symbol(symbol);
348
+ let id = symbolToPerpStaticInfo.get(cleanSymbol)?.id;
349
+ if (id == undefined) {
350
+ throw Error(`No perpetual found for symbol ${symbol}`);
351
+ }
352
+ return id;
353
+ }
354
+
355
+ protected static symbolToBytes4Symbol(symbol: string): string {
356
+ //split by dashes BTC-USD-MATIC
357
+ let symbols: string[] = symbol.split("-");
358
+ if (symbols.length != 3) {
359
+ throw Error(`Symbol ${symbol} not valid. Expecting CCY-CCY-CCY format`);
360
+ }
361
+ //transform into bytes4 currencies (without the space): "BTC", "USD", "MATC"
362
+ symbols = symbols.map((x) => {
363
+ let v = to4Chars(x);
364
+ v = v.replace(/\0/g, "");
365
+ return v;
366
+ });
367
+ // concatenate and find perpetual Id in map
368
+ return symbols[0] + "-" + symbols[1] + "-" + symbols[2];
369
+ }
370
+
371
+ private static _getByValue(map: any, searchValue: any) {
372
+ for (let [key, value] of map.entries()) {
373
+ if (value === searchValue) {
374
+ return key;
375
+ }
376
+ }
377
+ }
378
+
379
+ protected static fromSmartContractOrder(
380
+ order: SmartContractOrder,
381
+ symbolToPerpInfoMap: Map<string, PerpetualStaticInfo>
382
+ ): Order {
383
+ // find symbol of perpetual id
384
+ let symbol = PerpetualDataHandler._getByValue(symbolToPerpInfoMap, order.iPerpetualId);
385
+ let side = order.fAmount > 0 ? BUY_SIDE : SELL_SIDE;
386
+ let limitPrice, stopPrice;
387
+ let fLimitPrice: BigNumber | undefined = BigNumber.from(order.fLimitPrice);
388
+ if (fLimitPrice.eq(0) || fLimitPrice.eq(MAX_64x64)) {
389
+ limitPrice = undefined;
390
+ } else {
391
+ limitPrice = ABK64x64ToFloat(fLimitPrice);
392
+ }
393
+ let fStopPrice: BigNumber | undefined = BigNumber.from(order.fTriggerPrice);
394
+ if (fStopPrice.eq(0) || fStopPrice.eq(MAX_64x64)) {
395
+ stopPrice = undefined;
396
+ } else {
397
+ stopPrice = ABK64x64ToFloat(fStopPrice);
398
+ }
399
+ let userOrder: Order = {
400
+ symbol: symbol,
401
+ side: side,
402
+ type: PerpetualDataHandler._flagToOrderType(order),
403
+ quantity: Math.abs(ABK64x64ToFloat(BigNumber.from(order.fAmount))),
404
+ reduceOnly: containsFlag(BigNumber.from(order.flags), MASK_CLOSE_ONLY),
405
+ limitPrice: limitPrice,
406
+ keepPositionLvg: containsFlag(BigNumber.from(order.flags), MASK_KEEP_POS_LEVERAGE),
407
+ brokerFeeTbps: Number(order.brokerFeeTbps),
408
+ brokerAddr: order.brokerAddr,
409
+ brokerSignature: order.brokerSignature,
410
+ stopPrice: stopPrice,
411
+ leverage: ABK64x64ToFloat(BigNumber.from(order.fLeverage)),
412
+ deadline: Number(order.iDeadline),
413
+ timestamp: Number(order.createdTimestamp),
414
+ };
415
+ return userOrder;
416
+ }
417
+ /**
418
+ * Transform the convenient form of the order into a smart-contract accepted type of order
419
+ * @param order order type
420
+ * @param traderAddr address of the trader
421
+ * @param symbolToPerpetualMap mapping of symbol to perpetual Id
422
+ * @returns SmartContractOrder
423
+ */
424
+ protected static toSmartContractOrder(
425
+ order: Order,
426
+ traderAddr: string,
427
+ perpStaticInfo: Map<string, PerpetualStaticInfo>
428
+ ): SmartContractOrder {
429
+ let flags = PerpetualDataHandler._orderTypeToFlag(order);
430
+
431
+ let brokerSig = order.brokerSignature == undefined ? [] : order.brokerSignature;
432
+ let perpetualId = PerpetualDataHandler.symbolToPerpetualId(order.symbol, perpStaticInfo);
433
+ let fAmount: BigNumber;
434
+ if (order.side == BUY_SIDE) {
435
+ fAmount = floatToABK64x64(Math.abs(order.quantity));
436
+ } else if (order.side == SELL_SIDE) {
437
+ fAmount = floatToABK64x64(-Math.abs(order.quantity));
438
+ } else {
439
+ throw Error(`invalid side in order spec, use ${BUY_SIDE} or ${SELL_SIDE}`);
440
+ }
441
+ let fLimitPrice: BigNumber;
442
+ if (order.limitPrice == undefined) {
443
+ // we need to set the limit price to infinity or zero for
444
+ // the trade to go through
445
+ // Also: stop orders always have limits set, so even for this case
446
+ // we set the limit to 0 or infinity
447
+ fLimitPrice = order.side == BUY_SIDE ? MAX_64x64 : BigNumber.from(0);
448
+ } else {
449
+ fLimitPrice = floatToABK64x64(order.limitPrice);
450
+ }
451
+
452
+ let iDeadline = order.deadline == undefined ? Date.now() + ORDER_MAX_DURATION_SEC : order.deadline;
453
+ let fTriggerPrice = order.stopPrice == undefined ? BigNumber.from(0) : floatToABK64x64(order.stopPrice);
454
+ if (order.reduceOnly != undefined && order.reduceOnly == true) {
455
+ }
456
+ let smOrder: SmartContractOrder = {
457
+ flags: flags,
458
+ iPerpetualId: BigNumber.from(perpetualId),
459
+ brokerFeeTbps: order.brokerFeeTbps == undefined ? BigNumber.from(0) : BigNumber.from(order.brokerFeeTbps),
460
+ traderAddr: traderAddr,
461
+ brokerAddr: order.brokerAddr == undefined ? ZERO_ADDRESS : order.brokerAddr,
462
+ referrerAddr: ZERO_ADDRESS,
463
+ brokerSignature: brokerSig,
464
+ fAmount: fAmount,
465
+ fLimitPrice: fLimitPrice,
466
+ fTriggerPrice: fTriggerPrice,
467
+ fLeverage: order.leverage == undefined ? BigNumber.from(0) : floatToABK64x64(order.leverage),
468
+ iDeadline: BigNumber.from(iDeadline),
469
+ createdTimestamp: BigNumber.from(order.timestamp),
470
+ };
471
+ return smOrder;
472
+ }
473
+
474
+ private static _flagToOrderType(order: SmartContractOrder): string {
475
+ let hasTrigger = BigNumber.from(order.fTriggerPrice).eq(0);
476
+ let hasLimit = !BigNumber.from(order.fTriggerPrice).eq(0) || !BigNumber.from(order.fTriggerPrice).eq(MAX_64x64);
477
+ if (hasTrigger && hasLimit) {
478
+ return ORDER_TYPE_STOP_LIMIT;
479
+ } else if (hasTrigger && !hasLimit) {
480
+ return ORDER_TYPE_STOP_MARKET;
481
+ } else if (hasLimit && containsFlag(BigNumber.from(order.flags), MASK_LIMIT_ORDER)) {
482
+ return ORDER_TYPE_LIMIT;
483
+ } else {
484
+ return ORDER_TYPE_MARKET;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Determine the correct order flags based on the order-properties.
490
+ * Checks for some misspecifications.
491
+ * @param order order type
492
+ * @returns BigNumber flags
493
+ */
494
+ private static _orderTypeToFlag(order: Order): BigNumber {
495
+ let flag: BigNumber;
496
+ switch (order.type) {
497
+ case ORDER_TYPE_LIMIT:
498
+ flag = MASK_LIMIT_ORDER;
499
+ break;
500
+ case ORDER_TYPE_MARKET:
501
+ flag = MASK_MARKET_ORDER;
502
+ break;
503
+ case ORDER_TYPE_STOP_MARKET:
504
+ flag = MASK_STOP_ORDER;
505
+ break;
506
+ case ORDER_TYPE_STOP_LIMIT:
507
+ flag = MASK_STOP_ORDER;
508
+ break;
509
+ default: {
510
+ throw Error(`Order type ${order.type} not found.`);
511
+ }
512
+ }
513
+ if (order.keepPositionLvg != undefined && order.keepPositionLvg) {
514
+ flag = combineFlags(flag, MASK_KEEP_POS_LEVERAGE);
515
+ }
516
+ if (order.reduceOnly != undefined && order.reduceOnly) {
517
+ flag = combineFlags(flag, MASK_CLOSE_ONLY);
518
+ }
519
+ if ((order.type == ORDER_TYPE_LIMIT || order.type == ORDER_TYPE_STOP_LIMIT) && order.limitPrice == undefined) {
520
+ throw Error(`Order type ${order.type} requires limit price.`);
521
+ }
522
+ if ((order.type == ORDER_TYPE_STOP_MARKET || order.type == ORDER_TYPE_STOP_LIMIT) && order.stopPrice == undefined) {
523
+ throw Error(`Order type ${order.type} requires trigger price.`);
524
+ }
525
+ if ((order.type == ORDER_TYPE_MARKET || order.type == ORDER_TYPE_LIMIT) && order.stopPrice != undefined) {
526
+ throw Error(`Order type ${order.type} has no trigger price.`);
527
+ }
528
+ if (order.type != ORDER_TYPE_MARKET && order.stopPrice != undefined) {
529
+ throw Error(`Order type ${order.type} has no trigger price.`);
530
+ }
531
+ return flag;
532
+ }
533
+
534
+ /**
535
+ * Read config file into NodeSDKConfig interface
536
+ * @param fileLocation json-file with required variables for config
537
+ * @returns NodeSDKConfig
538
+ */
539
+ public static readSDKConfig(fileLocation: string): NodeSDKConfig {
540
+ if (fileLocation == DEFAULT_CONFIG_MAINNET_NAME) {
541
+ fileLocation = DEFAULT_CONFIG_MAINNET;
542
+ } else if (fileLocation == DEFAULT_CONFIG_TESTNET_NAME) {
543
+ fileLocation = DEFAULT_CONFIG_TESTNET;
544
+ }
545
+ let configFile = require(fileLocation);
546
+ let config: NodeSDKConfig = <NodeSDKConfig>configFile;
547
+ return config;
548
+ }
549
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { BigNumber } from "ethers";
2
+
3
+ const ethers = require("ethers");
4
+
5
+ function _isVocal(char: string) {
6
+ char = char.toLowerCase();
7
+ return char == "a" || char == "e" || char == "i" || char == "o" || char == "u";
8
+ }
9
+
10
+ /**
11
+ *
12
+ * @param s string to shorten/extend to 4 characters
13
+ * @returns string with 4 characters (or characters + null chars)
14
+ */
15
+ export function to4Chars(s: string) {
16
+ while (s.length < 4) {
17
+ s = s + "\0";
18
+ }
19
+ let k = s.length - 1;
20
+ while (s.length > 4 && k >= 0) {
21
+ // chop off vocals from the end of string
22
+ // e.g. MATIC -> MATC
23
+ if (_isVocal(s.charAt(k))) {
24
+ s = s.substring(0, k) + s.substring(k + 1, s.length);
25
+ }
26
+ k--;
27
+ }
28
+ s = s.substring(0, 4);
29
+ return s;
30
+ }
31
+
32
+ /**
33
+ * Converts string into 4-character bytes4
34
+ * uses to4Chars to first convert the string into
35
+ * 4 characters.
36
+ * Resulting buffer can be used with smart contract to
37
+ * identify tokens (BTC, USDC, MATIC etc.)
38
+ * @param s string to encode into bytes4
39
+ * @returns buffer
40
+ */
41
+ export function toBytes4(s: string): Buffer {
42
+ s = to4Chars(s);
43
+ let valBuff: Buffer = Buffer.from(s, "ascii");
44
+ return valBuff;
45
+ }
46
+
47
+ /**
48
+ * Decodes a buffer encoded with toBytes4 into
49
+ * a string. The string is the result of to4Chars of the
50
+ * originally encoded string stripped from null-chars
51
+ * @param b correctly encoded bytes4 buffer using toBytes4
52
+ * @returns string decoded into to4Chars-type string without null characters
53
+ */
54
+ export function fromBytes4(b: Buffer): string {
55
+ let val: string = b.toString("ascii");
56
+ val = val.replace(/\0/g, "");
57
+ return val;
58
+ }
59
+
60
+ /**
61
+ * Decodes the bytes4 encoded string received from the
62
+ * smart contract as a hex-number in string-format
63
+ * @param s string representing a hex-number ("0x...")
64
+ * @returns x of to4Chars(x) stripped from null-chars,
65
+ * where x was originally encoded and
66
+ * returned by the smart contract as bytes4
67
+ */
68
+ export function fromBytes4HexString(s: string): string {
69
+ let res = "";
70
+ for (let k = 2; k < s.length; k = k + 2) {
71
+ res = res + String.fromCharCode(parseInt(s.substring(k, k + 2), 16));
72
+ }
73
+ res = res.replace(/\0/g, "");
74
+ return res;
75
+ }
76
+
77
+ export function combineFlags(f1: BigNumber, f2: BigNumber): BigNumber {
78
+ return BigNumber.from(parseInt(f1.toString()) | parseInt(f2.toString()));
79
+ }
80
+
81
+ export function containsFlag(f1: BigNumber, f2: BigNumber): boolean {
82
+ return (parseInt(f1.toString()) & parseInt(f2.toString())) > 0;
83
+ }