@d8x/perpetuals-sdk 0.6.4 → 0.6.6

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 (50) hide show
  1. package/dist/cjs/version.d.ts +1 -1
  2. package/dist/cjs/version.js +2 -2
  3. package/dist/cjs/version.js.map +1 -1
  4. package/dist/esm/version.d.ts +1 -1
  5. package/dist/esm/version.js +1 -1
  6. package/dist/esm/version.js.map +1 -1
  7. package/package.json +3 -2
  8. package/src/abi/ERC20.json +288 -0
  9. package/src/abi/IPerpetualManager.json +5888 -0
  10. package/src/abi/LimitOrderBook.json +1062 -0
  11. package/src/abi/LimitOrderBookFactory.json +161 -0
  12. package/src/abi/MockTokenSwap.json +186 -0
  13. package/src/abi/ShareToken.json +428 -0
  14. package/src/accountTrade.ts +428 -0
  15. package/src/brokerTool.ts +555 -0
  16. package/src/config/defaultConfig.json +62 -0
  17. package/src/config/mockSwap.json +6 -0
  18. package/src/config/priceFeedConfig.json +104 -0
  19. package/src/config/symbolList.json +13 -0
  20. package/src/contracts/ERC20.ts +444 -0
  21. package/src/contracts/IPerpetualManager.ts +7227 -0
  22. package/src/contracts/LimitOrderBook.ts +1251 -0
  23. package/src/contracts/LimitOrderBookFactory.ts +348 -0
  24. package/src/contracts/MockTokenSwap.ts +373 -0
  25. package/src/contracts/ShareToken.ts +695 -0
  26. package/src/contracts/common.ts +44 -0
  27. package/src/contracts/factories/ERC20__factory.ts +306 -0
  28. package/src/contracts/factories/IPerpetualManager__factory.ts +5912 -0
  29. package/src/contracts/factories/LimitOrderBookFactory__factory.ts +189 -0
  30. package/src/contracts/factories/LimitOrderBook__factory.ts +1086 -0
  31. package/src/contracts/factories/MockTokenSwap__factory.ts +207 -0
  32. package/src/contracts/factories/ShareToken__factory.ts +449 -0
  33. package/src/contracts/factories/index.ts +9 -0
  34. package/src/contracts/index.ts +16 -0
  35. package/src/d8XMath.ts +376 -0
  36. package/src/index.ts +29 -0
  37. package/src/liquidatorTool.ts +270 -0
  38. package/src/liquidityProviderTool.ts +148 -0
  39. package/src/marketData.ts +1310 -0
  40. package/src/nodeSDKTypes.ts +332 -0
  41. package/src/orderReferrerTool.ts +516 -0
  42. package/src/perpetualDataHandler.ts +1161 -0
  43. package/src/perpetualEventHandler.ts +455 -0
  44. package/src/priceFeeds.ts +382 -0
  45. package/src/traderDigests.ts +86 -0
  46. package/src/traderInterface.ts +172 -0
  47. package/src/triangulator.ts +105 -0
  48. package/src/utils.ts +134 -0
  49. package/src/version.ts +1 -0
  50. package/src/writeAccessHandler.ts +139 -0
@@ -0,0 +1,382 @@
1
+ import { BigNumber } from "@ethersproject/bignumber";
2
+ import PerpetualDataHandler from "./perpetualDataHandler";
3
+ import Triangulator from "./triangulator";
4
+ import { PriceFeedConfig, PriceFeedSubmission, PriceFeedFormat, PythLatestPriceFeed } from "./nodeSDKTypes";
5
+ import { decNToFloat } from "./d8XMath";
6
+ import { Buffer } from "buffer";
7
+
8
+ /**
9
+ * This class communicates with the REST API that provides price-data that is
10
+ * to be submitted to the smart contracts for certain functions such as
11
+ * trader liquidations, trade executions, change of trader margin amount.
12
+ */
13
+ export default class PriceFeeds {
14
+ private config: PriceFeedConfig;
15
+ private feedEndpoints: Array<string>; //feedEndpoints[endpointId] = endpointstring
16
+ private feedInfo: Map<string, { symbol: string; endpointId: number }>; // priceFeedId -> symbol, endpointId
17
+ private dataHandler: PerpetualDataHandler;
18
+ // store triangulation paths given the price feeds
19
+ private triangulations: Map<string, [string[], boolean[]]>;
20
+ private THRESHOLD_MARKET_CLOSED_SEC = 15; // smallest lag for which we consider the market as being closed
21
+
22
+ constructor(dataHandler: PerpetualDataHandler, priceFeedConfigNetwork: string) {
23
+ let configs = <PriceFeedConfig[]>require("./config/priceFeedConfig.json");
24
+ this.config = PriceFeeds._selectConfig(configs, priceFeedConfigNetwork);
25
+ [this.feedInfo, this.feedEndpoints] = PriceFeeds._constructFeedInfo(this.config);
26
+ this.dataHandler = dataHandler;
27
+ this.triangulations = new Map<string, [string[], boolean[]]>();
28
+ }
29
+
30
+ /**
31
+ * Pre-processing of triangulations for symbols, given the price feeds
32
+ * @param symbols set of symbols we want to triangulate from price feeds
33
+ */
34
+ public initializeTriangulations(symbols: Set<string>) {
35
+ let feedSymbols = new Array<string>();
36
+ for (let [, value] of this.feedInfo) {
37
+ feedSymbols.push(value.symbol);
38
+ }
39
+ for (let symbol of symbols.values()) {
40
+ let triangulation = Triangulator.triangulate(feedSymbols, symbol);
41
+ this.triangulations.set(symbol, triangulation);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Get required information to be able to submit a blockchain transaction with price-update
47
+ * such as trade execution, liquidation
48
+ * @param symbol symbol of perpetual, e.g., BTC-USD-MATIC
49
+ * @returns PriceFeedSubmission, index prices, market closed information
50
+ */
51
+ public async fetchFeedPriceInfoAndIndicesForPerpetual(
52
+ symbol: string
53
+ ): Promise<{ submission: PriceFeedSubmission; pxS2S3: [number, number]; mktClosed: [boolean, boolean] }> {
54
+ let indexSymbols = this.dataHandler.getIndexSymbols(symbol);
55
+ // fetch prices from required price-feeds (REST)
56
+ let submission: PriceFeedSubmission = await this.fetchLatestFeedPriceInfoForPerpetual(symbol);
57
+ // calculate index-prices from price-feeds
58
+ let [_idxPrices, _mktClosed] = this.calculateTriangulatedPricesFromFeedInfo(
59
+ indexSymbols.filter((x) => x != ""),
60
+ submission
61
+ );
62
+ let idxPrices: [number, number] = [_idxPrices[0], 0];
63
+ let mktClosed: [boolean, boolean] = [_mktClosed[0], false];
64
+ if (idxPrices.length > 1) {
65
+ idxPrices[1] = _idxPrices[1];
66
+ mktClosed[1] = _mktClosed[1];
67
+ }
68
+ return { submission: submission, pxS2S3: idxPrices, mktClosed: mktClosed };
69
+ }
70
+
71
+ /**
72
+ * Get all prices/isMarketClosed for the provided symbols via
73
+ * "latest_price_feeds" and triangulation. Triangulation must be defined in config, unless
74
+ * it is a direct price feed.
75
+ * @returns map of feed-price symbol to price/isMarketClosed
76
+ */
77
+ public async fetchPrices(symbols: string[]): Promise<Map<string, [number, boolean]>> {
78
+ let feedPrices = await this.fetchAllFeedPrices();
79
+ let [prices, mktClosed] = this.triangulatePricesFromFeedPrices(symbols, feedPrices);
80
+ let symMap = new Map<string, [number, boolean]>();
81
+ for (let k = 0; k < symbols.length; k++) {
82
+ symMap.set(symbols[k], [prices[k], mktClosed[k]]);
83
+ }
84
+ return symMap;
85
+ }
86
+
87
+ /**
88
+ * Get index prices and market closed information for the given perpetual
89
+ * @param symbol perpetual symbol such as ETH-USD-MATIC
90
+ * @returns Index prices and market closed information
91
+ */
92
+ public async fetchPricesForPerpetual(symbol: string): Promise<{ idxPrices: number[]; mktClosed: boolean[] }> {
93
+ let indexSymbols = this.dataHandler.getIndexSymbols(symbol).filter((x) => x != "");
94
+ // determine relevant price feeds
95
+ let feedSymbols = new Array<string>();
96
+ for (let sym of indexSymbols) {
97
+ if (sym != "") {
98
+ let triang: [string[], boolean[]] | undefined = this.triangulations.get(sym);
99
+ if (triang == undefined) {
100
+ // no triangulation defined, so symbol must be a feed (unless misconfigured)
101
+ feedSymbols.push(sym);
102
+ } else {
103
+ // push all required feeds to array
104
+ triang[0].map((feedSym) => feedSymbols.push(feedSym));
105
+ }
106
+ }
107
+ }
108
+ // get all feed prices
109
+ let feedPrices = await this.fetchFeedPrices(feedSymbols);
110
+ // triangulate
111
+ let [prices, mktClosed] = this.triangulatePricesFromFeedPrices(indexSymbols, feedPrices);
112
+ // ensure we return an array of 2 in all cases
113
+ if (prices.length == 1) {
114
+ prices.push(0);
115
+ mktClosed.push(false);
116
+ }
117
+ return { idxPrices: prices, mktClosed: mktClosed };
118
+ }
119
+
120
+ /**
121
+ * Fetch the provided feed prices and bool whether market is closed or open
122
+ * - requires the feeds to be defined in priceFeedConfig.json
123
+ * - if undefined, all feeds are queried
124
+ * @param symbols array of feed-price symbols (e.g., [btc-usd, eth-usd]) or undefined
125
+ * @returns mapping symbol-> [price, isMarketClosed]
126
+ */
127
+ public async fetchFeedPrices(symbols?: string[]): Promise<Map<string, [number, boolean]>> {
128
+ let queries = new Array<string>(this.feedEndpoints.length);
129
+ let symbolsOfEndpoint: string[][] = [];
130
+ for (let j = 0; j < queries.length; j++) {
131
+ symbolsOfEndpoint.push([]);
132
+ }
133
+ for (let k = 0; k < this.config.ids.length; k++) {
134
+ let currFeed = this.config.ids[k];
135
+ if (symbols != undefined && !symbols.includes(currFeed.symbol)) {
136
+ continue;
137
+ }
138
+ // feedInfo: Map<string, {symbol:string, endpointId: number}>; // priceFeedId -> symbol, endpointId
139
+ let endpointId = this.feedInfo.get(currFeed.id)!.endpointId;
140
+ symbolsOfEndpoint[endpointId].push(currFeed.symbol);
141
+ if (queries[endpointId] == undefined) {
142
+ // each id can have a different endpoint, but we cluster
143
+ // the queries into one per endpoint
144
+ queries[endpointId] = this.feedEndpoints[endpointId] + "/latest_price_feeds?";
145
+ }
146
+ queries[endpointId] = queries[endpointId] + "ids[]=" + currFeed.id + "&";
147
+ }
148
+ let resultPrices = new Map<string, [number, boolean]>();
149
+ for (let k = 0; k < queries.length; k++) {
150
+ if (queries[k] == undefined) {
151
+ continue;
152
+ }
153
+ let [, pxInfo]: [string[], PriceFeedFormat[]] = await this.fetchPriceQuery(queries[k]);
154
+ let tsSecNow = Math.round(Date.now() / 1000);
155
+ for (let j = 0; j < pxInfo.length; j++) {
156
+ let price = decNToFloat(BigNumber.from(pxInfo[j].price), -pxInfo[j].expo);
157
+ let isMarketClosed = tsSecNow - pxInfo[j].publish_time > this.THRESHOLD_MARKET_CLOSED_SEC;
158
+ resultPrices.set(symbolsOfEndpoint[k][j], [price, isMarketClosed]);
159
+ }
160
+ }
161
+ return resultPrices;
162
+ }
163
+
164
+ /**
165
+ * Get all configured feed prices via "latest_price_feeds"
166
+ * @returns map of feed-price symbol to price/isMarketClosed
167
+ */
168
+ public async fetchAllFeedPrices(): Promise<Map<string, [number, boolean]>> {
169
+ return this.fetchFeedPrices();
170
+ }
171
+
172
+ /**
173
+ * Get the latest prices for a given perpetual from the offchain oracle
174
+ * networks
175
+ * @param symbol perpetual symbol of the form BTC-USD-MATIC
176
+ * @returns array of price feed updates that can be submitted to the smart contract
177
+ * and corresponding price information
178
+ */
179
+ public async fetchLatestFeedPriceInfoForPerpetual(symbol: string): Promise<PriceFeedSubmission> {
180
+ let feedIds = this.dataHandler.getPriceIds(symbol);
181
+ let queries = new Array<string>(this.feedEndpoints.length);
182
+ // we need to preserve the order of the price feeds
183
+ let orderEndpointNumber = new Array<number>();
184
+ // count how many prices per endpoint
185
+ let idCountPriceFeeds = new Array<number>(this.feedEndpoints.length);
186
+ let symbols = new Array<string>();
187
+ for (let k = 0; k < feedIds.length; k++) {
188
+ let info = this.feedInfo.get(feedIds[k]);
189
+ if (info == undefined) {
190
+ throw new Error(`priceFeeds: config for symbol ${symbol} insufficient`);
191
+ }
192
+ let id = info.endpointId;
193
+ symbols.push(info.symbol);
194
+ if (queries[id] == undefined) {
195
+ // each id can have a different endpoint, but we cluster
196
+ // the queries into one per endpoint
197
+ queries[id] = this.feedEndpoints[id] + "/latest_price_feeds?target_chain=default&";
198
+ idCountPriceFeeds[id] = 0;
199
+ }
200
+ queries[id] = queries[id] + "ids[]=" + feedIds[k] + "&";
201
+ orderEndpointNumber.push(id * 100 + idCountPriceFeeds[id]);
202
+ idCountPriceFeeds[id] = idCountPriceFeeds[id] + 1;
203
+ }
204
+
205
+ let data = await Promise.all(
206
+ queries.map(async (q) => {
207
+ if (q != undefined) {
208
+ return this.fetchVAAQuery(q);
209
+ } else {
210
+ return [[], []];
211
+ }
212
+ })
213
+ );
214
+
215
+ // re-order arrays so we preserve the order of the feeds
216
+ const priceFeedUpdates = new Array<string>();
217
+ const prices = new Array<number>();
218
+ const mktClosed = new Array<boolean>();
219
+ const timestamps = new Array<number>();
220
+ const tsSecNow = Math.round(Date.now() / 1000);
221
+ for (let k = 0; k < orderEndpointNumber.length; k++) {
222
+ let endpointId = Math.floor(orderEndpointNumber[k] / 100);
223
+ let idWithinEndpoint = orderEndpointNumber[k] - 100 * endpointId;
224
+ priceFeedUpdates.push(data[endpointId][0][idWithinEndpoint]);
225
+ let pxInfo: PriceFeedFormat = data[endpointId][1][idWithinEndpoint];
226
+ let price = decNToFloat(BigNumber.from(pxInfo.price), -pxInfo.expo);
227
+ let isMarketClosed = tsSecNow - pxInfo.publish_time > this.THRESHOLD_MARKET_CLOSED_SEC;
228
+ mktClosed.push(isMarketClosed);
229
+ prices.push(price);
230
+ timestamps.push(pxInfo.publish_time);
231
+ }
232
+
233
+ return {
234
+ symbols: symbols,
235
+ priceFeedVaas: priceFeedUpdates,
236
+ prices: prices,
237
+ isMarketClosed: mktClosed,
238
+ timestamps: timestamps,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Extract pair-prices from underlying price feeds via triangulation
244
+ * The function either needs a direct price feed or a defined triangulation to succesfully
245
+ * return a triangulated price
246
+ * @param symbols array of pairs for which we want prices, e.g., [BTC-USDC, ETH-USD]
247
+ * @param feeds data obtained via fetchLatestFeedPriceInfo or fetchLatestFeedPrices
248
+ * @returns array of prices with same order as symbols
249
+ */
250
+ public calculateTriangulatedPricesFromFeedInfo(symbols: string[], feeds: PriceFeedSubmission): [number[], boolean[]] {
251
+ let priceMap = new Map<string, [number, boolean]>();
252
+ for (let j = 0; j < feeds.prices.length; j++) {
253
+ priceMap.set(feeds.symbols[j], [feeds.prices[j], feeds.isMarketClosed[j]]);
254
+ }
255
+ return this.triangulatePricesFromFeedPrices(symbols, priceMap);
256
+ }
257
+ /**
258
+ * Extract pair-prices from underlying price feeds via triangulation
259
+ * The function either needs a direct price feed or a defined triangulation to succesfully
260
+ * return a triangulated price
261
+ * @param symbols array of pairs for which we want prices, e.g., [BTC-USDC, ETH-USD]
262
+ * @param feeds data obtained via fetchLatestFeedPriceInfo or fetchLatestFeedPrices
263
+ * @returns array of prices with same order as symbols
264
+ */
265
+ public triangulatePricesFromFeedPrices(
266
+ symbols: string[],
267
+ feedPriceMap: Map<string, [number, boolean]>
268
+ ): [number[], boolean[]] {
269
+ let prices = new Array<number>();
270
+ let mktClosed = new Array<boolean>();
271
+ for (let k = 0; k < symbols.length; k++) {
272
+ let triangulation: [string[], boolean[]] | undefined = this.triangulations.get(symbols[k]);
273
+ if (triangulation == undefined) {
274
+ let feedPrice = feedPriceMap.get(symbols[k]);
275
+ if (feedPrice == undefined) {
276
+ throw new Error(`PriceFeeds: no triangulation defined for ${symbols[k]}`);
277
+ } else {
278
+ prices.push(feedPrice[0]); //price
279
+ mktClosed.push(feedPrice[1]); //market closed?
280
+ continue;
281
+ }
282
+ }
283
+ let [px, isMktClosed]: [number, boolean] = Triangulator.calculateTriangulatedPrice(triangulation, feedPriceMap);
284
+ prices.push(px);
285
+ mktClosed.push(isMktClosed);
286
+ }
287
+ return [prices, mktClosed];
288
+ }
289
+
290
+ /**
291
+ * Queries the REST endpoint and returns parsed VAA price data
292
+ * @param query query price-info from endpoint
293
+ * @returns vaa and price info
294
+ */
295
+ private async fetchVAAQuery(query: string): Promise<[string[], PriceFeedFormat[]]> {
296
+ const headers = { headers: { "Content-Type": "application/json" } };
297
+ let response = await fetch(query, headers);
298
+ if (!response.ok) {
299
+ throw new Error(`Failed to fetch posts (${response.status}): ${response.statusText}`);
300
+ }
301
+ let values = (await response.json()) as Array<PythLatestPriceFeed>;
302
+ const priceFeedUpdates = new Array<string>();
303
+ const px = new Array<PriceFeedFormat>();
304
+ for (let k = 0; k < values.length; k++) {
305
+ const vaa = values[k].vaa;
306
+ priceFeedUpdates.push("0x" + Buffer.from(vaa, "base64").toString("hex"));
307
+ px.push(values[k].price);
308
+ }
309
+ return [priceFeedUpdates, px];
310
+ }
311
+
312
+ /**
313
+ * Queries the REST endpoint and returns parsed price data
314
+ * @param query query price-info from endpoint
315
+ * @returns vaa and price info
316
+ */
317
+ public async fetchPriceQuery(query: string): Promise<[string[], PriceFeedFormat[]]> {
318
+ const headers = { headers: { "Content-Type": "application/json" } };
319
+ let response = await fetch(query, headers);
320
+ if (!response.ok) {
321
+ throw new Error(`Failed to fetch posts (${response.status}): ${response.statusText}`);
322
+ }
323
+ let values = await response.json();
324
+ const priceFeedUpdates = new Array<string>();
325
+ const px = new Array<PriceFeedFormat>();
326
+ for (let k = 0; k < values.length; k++) {
327
+ priceFeedUpdates.push("0x" + Buffer.from(values[k].id, "base64").toString("hex"));
328
+ px.push(values[k].price as PriceFeedFormat);
329
+ }
330
+ return [priceFeedUpdates, px];
331
+ }
332
+
333
+ /**
334
+ * Searches for configuration for given network
335
+ * @param configs pricefeed configuration from json
336
+ * @param network e.g. testnet
337
+ * @returns selected configuration
338
+ */
339
+ static _selectConfig(configs: PriceFeedConfig[], network: string): PriceFeedConfig {
340
+ let k = 0;
341
+ while (k < configs.length) {
342
+ if (configs[k].network == network) {
343
+ return configs[k];
344
+ }
345
+ k = k + 1;
346
+ }
347
+ throw new Error(`PriceFeeds: config not found for network ${network}`);
348
+ }
349
+
350
+ /**
351
+ * Wraps configuration into convenient data-structure
352
+ * @param config configuration for the selected network
353
+ * @returns feedInfo-map and endPoints-array
354
+ */
355
+ static _constructFeedInfo(config: PriceFeedConfig): [Map<string, { symbol: string; endpointId: number }>, string[]] {
356
+ let feed = new Map<string, { symbol: string; endpointId: number }>();
357
+ let endpointId = -1;
358
+ let type = "";
359
+ let feedEndpoints = new Array<string>();
360
+ for (let k = 0; k < config.endpoints.length; k++) {
361
+ feedEndpoints.push(config.endpoints[k].endpoint);
362
+ }
363
+ for (let k = 0; k < config.ids.length; k++) {
364
+ if (type != config.ids[k].type) {
365
+ type = config.ids[k].type;
366
+ let j = 0;
367
+ while (j < config.endpoints.length) {
368
+ if (config.endpoints[j].type == type) {
369
+ endpointId = j;
370
+ j = config.endpoints.length;
371
+ }
372
+ j++;
373
+ }
374
+ if (config.endpoints[endpointId].type != type) {
375
+ throw new Error(`priceFeeds: no enpoint found for ${type} check priceFeedConfig`);
376
+ }
377
+ }
378
+ feed.set(config.ids[k].id, { symbol: config.ids[k].symbol.toUpperCase(), endpointId: endpointId });
379
+ }
380
+ return [feed, feedEndpoints];
381
+ }
382
+ }
@@ -0,0 +1,86 @@
1
+ import { defaultAbiCoder } from "@ethersproject/abi";
2
+ import { concat } from "@ethersproject/bytes";
3
+ import { keccak256 } from "@ethersproject/keccak256";
4
+ import { toUtf8Bytes } from "@ethersproject/strings";
5
+ import { Buffer } from "buffer";
6
+ import { SmartContractOrder } from "./nodeSDKTypes";
7
+
8
+ export default class TraderDigests {
9
+ /**
10
+ * Creates an order-id from the digest. Order-id is the 'digest' used in the smart contract.
11
+ * @param digest created with _createDigest
12
+ * @returns orderId string
13
+ * @ignore
14
+ */
15
+ public createOrderId(digest: string): string {
16
+ let digestBuffer = Buffer.from(digest.substring(2, digest.length), "hex");
17
+ const messagePrefix = "\x19Ethereum Signed Message:\n";
18
+ let tmp = concat([toUtf8Bytes(messagePrefix), toUtf8Bytes(String(digestBuffer.length)), digestBuffer]);
19
+ // see: https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/hash/src.ts/message.ts#L7
20
+ return keccak256(tmp);
21
+ }
22
+
23
+ /**
24
+ * Creates a digest (order-id)
25
+ * @param order smart-contract-type order
26
+ * @param chainId chainId of network
27
+ * @param isNewOrder true unless we cancel
28
+ * @param signer ethereum-type wallet
29
+ * @param proxyAddress address of the contract
30
+ * @returns digest
31
+ * @ignore
32
+ */
33
+ public createDigest(order: SmartContractOrder, chainId: number, isNewOrder: boolean, proxyAddress: string): string {
34
+ const NAME = "Perpetual Trade Manager";
35
+ const DOMAIN_TYPEHASH = keccak256(
36
+ Buffer.from("EIP712Domain(string name,uint256 chainId,address verifyingContract)")
37
+ );
38
+ let domainSeparator = keccak256(
39
+ defaultAbiCoder.encode(
40
+ ["bytes32", "bytes32", "uint256", "address"],
41
+ [DOMAIN_TYPEHASH, keccak256(Buffer.from(NAME)), chainId, proxyAddress]
42
+ )
43
+ );
44
+ const TRADE_ORDER_TYPEHASH = keccak256(
45
+ Buffer.from(
46
+ "Order(uint24 iPerpetualId,uint16 brokerFeeTbps,address traderAddr,address brokerAddr,int128 fAmount,int128 fLimitPrice,int128 fTriggerPrice,uint32 iDeadline,uint32 flags,uint16 leverageTDR,uint32 executionTimestamp)"
47
+ )
48
+ );
49
+ let structHash = keccak256(
50
+ defaultAbiCoder.encode(
51
+ [
52
+ "bytes32",
53
+ "uint24",
54
+ "uint16",
55
+ "address",
56
+ "address",
57
+ "int128",
58
+ "int128",
59
+ "int128",
60
+ "uint64",
61
+ "uint32",
62
+ "uint16",
63
+ "uint64",
64
+ ],
65
+ [
66
+ TRADE_ORDER_TYPEHASH,
67
+ order.iPerpetualId,
68
+ order.brokerFeeTbps,
69
+ order.traderAddr,
70
+ order.brokerAddr,
71
+ order.fAmount,
72
+ order.fLimitPrice,
73
+ order.fTriggerPrice,
74
+ order.iDeadline,
75
+ order.flags,
76
+ order.leverageTDR,
77
+ order.executionTimestamp,
78
+ ]
79
+ )
80
+ );
81
+ let digest = keccak256(
82
+ defaultAbiCoder.encode(["bytes32", "bytes32", "bool"], [domainSeparator, structHash, isNewOrder])
83
+ );
84
+ return digest;
85
+ }
86
+ }
@@ -0,0 +1,172 @@
1
+ import { CallOverrides, Contract } from "@ethersproject/contracts";
2
+ import { ABK64x64ToFloat } from "./d8XMath";
3
+ import MarketData from "./marketData";
4
+ import { ClientOrder, NodeSDKConfig, Order, SmartContractOrder, ZERO_ORDER_ID } from "./nodeSDKTypes";
5
+ import PerpetualDataHandler from "./perpetualDataHandler";
6
+ import TraderDigests from "./traderDigests";
7
+ /**
8
+ * Interface that can be used by front-end that wraps all private functions
9
+ * so that signatures can be handled in frontend via wallet
10
+ * @extends MarketData
11
+ */
12
+ export default class TraderInterface extends MarketData {
13
+ public digestTool: TraderDigests;
14
+
15
+ // accTrade.order(order)
16
+ // cancelOrder(symbol: string, orderId: string)
17
+ // accTrade.setAllowance
18
+ // accTrade.getOrderIds("MATIC-USD-MATIC")
19
+
20
+ /**
21
+ * Constructor
22
+ * @param {NodeSDKConfig} config Configuration object, see
23
+ * PerpetualDataHandler.readSDKConfig.
24
+ */
25
+ constructor(config: NodeSDKConfig) {
26
+ super(config);
27
+ this.digestTool = new TraderDigests();
28
+ }
29
+
30
+ /**
31
+ * Get the fee that is charged to the trader for a given broker (can be ZERO-address),
32
+ * without broker fee
33
+ * @param poolSymbolName pool currency (e.g. MATIC)
34
+ * @param traderAddr address of trader
35
+ * @param brokerAddr address of broker
36
+ * @returns fee (in decimals) that is charged by exchange (without broker)
37
+ */
38
+ public async queryExchangeFee(
39
+ poolSymbolName: string,
40
+ traderAddr: string,
41
+ brokerAddr: string,
42
+ overrides?: CallOverrides
43
+ ): Promise<number> {
44
+ if (this.proxyContract == null) {
45
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
46
+ }
47
+ let poolId = PerpetualDataHandler._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos);
48
+ let feeTbps = await this.proxyContract.queryExchangeFee(poolId, traderAddr, brokerAddr, overrides || {});
49
+ return feeTbps / 100_000;
50
+ }
51
+
52
+ /**
53
+ *
54
+ * @param poolSymbolName pool symbol, e.g. MATIC
55
+ * @param traderAddr address of the trader
56
+ * @returns volume in USD
57
+ */
58
+ public async getCurrentTraderVolume(
59
+ poolSymbolName: string,
60
+ traderAddr: string,
61
+ overrides?: CallOverrides
62
+ ): Promise<number> {
63
+ if (this.proxyContract == null) {
64
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
65
+ }
66
+ let poolId = PerpetualDataHandler._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos);
67
+ let volume = await this.proxyContract.getCurrentTraderVolume(poolId, traderAddr, overrides || {});
68
+ return ABK64x64ToFloat(volume);
69
+ }
70
+
71
+ /**
72
+ * Get digest to cancel an order. Digest needs to be signed and submitted via
73
+ * orderBookContract.cancelOrder(orderId, signature);
74
+ * @param symbol
75
+ * @param orderId
76
+ * @returns tuple of digest which the trader needs to sign and address of order book contract
77
+ */
78
+ public async cancelOrderDigest(
79
+ symbol: string,
80
+ orderId: string,
81
+ overrides?: CallOverrides
82
+ ): Promise<{ digest: string; OBContractAddr: string }> {
83
+ if (this.proxyContract == null) {
84
+ throw Error("no proxy contract initialized. Use createProxyInstance().");
85
+ }
86
+ let orderBookContract = this.getOrderBookContract(symbol);
87
+ let scOrder: SmartContractOrder = await orderBookContract.orderOfDigest(orderId, overrides || {});
88
+ let digest = this.digestTool.createDigest(scOrder, this.chainId, false, this.proxyAddr);
89
+ return { digest: digest, OBContractAddr: orderBookContract.address };
90
+ }
91
+
92
+ /**
93
+ * Get the order book address for a perpetual
94
+ * @param symbol symbol (e.g. MATIC-USD-MATIC)
95
+ * @returns order book address for the perpetual
96
+ */
97
+ public getOrderBookAddress(symbol: string): string {
98
+ let orderBookContract: Contract = this.getOrderBookContract(symbol);
99
+ return orderBookContract.address;
100
+ }
101
+
102
+ /**
103
+ * createSmartContractOrder from user-friendly order
104
+ * @param order order struct
105
+ * @param traderAddr address of trader
106
+ * @returns Smart contract type order struct
107
+ */
108
+ public createSmartContractOrder(order: Order, traderAddr: string): SmartContractOrder {
109
+ let scOrder = TraderInterface.toSmartContractOrder(order, traderAddr, this.symbolToPerpStaticInfo);
110
+ return scOrder;
111
+ }
112
+
113
+ /**
114
+ * Create smart contract order and digest that the trader signs.
115
+ * await orderBookContract.postOrder(scOrder, signature, { gasLimit: gasLimit });
116
+ * Order must contain broker fee and broker address if there is supposed to be a broker.
117
+ * @param scOrder smart contract order struct (get from order via createSCOrder)
118
+ * @returns digest that the trader has to sign
119
+ */
120
+ public orderDigest(scOrder: SmartContractOrder): string {
121
+ if (this.proxyContract == null) {
122
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
123
+ }
124
+ let digest = this.digestTool.createDigest(scOrder, this.chainId, true, this.proxyContract.address);
125
+ return digest;
126
+ }
127
+
128
+ /**
129
+ * Get the ABI of a method in the proxy contract
130
+ * @param method Name of the method
131
+ * @returns ABI as a single string
132
+ */
133
+ public getProxyABI(method: string): string {
134
+ if (this.proxyContract == null) {
135
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
136
+ }
137
+ return PerpetualDataHandler._getABIFromContract(this.proxyContract, method);
138
+ }
139
+
140
+ /**
141
+ * Get the ABI of a method in the Limit Order Book contract corresponding to a given symbol.
142
+ * @param symbol Symbol of the form MATIC-USD-MATIC
143
+ * @param method Name of the method
144
+ * @returns ABI as a single string
145
+ */
146
+ public getOrderBookABI(symbol: string, method: string): string {
147
+ if (this.proxyContract == null) {
148
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
149
+ }
150
+ let orderBookContract: Contract = this.getOrderBookContract(symbol);
151
+ return PerpetualDataHandler._getABIFromContract(orderBookContract, method);
152
+ }
153
+
154
+ public static chainOrders(orders: SmartContractOrder[], ids: string[]): ClientOrder[] {
155
+ // add dependency
156
+ let obOrders: ClientOrder[] = new Array<ClientOrder>(orders.length);
157
+ if (orders.length == 1 || orders.length > 3) {
158
+ // nothing to add
159
+ obOrders = orders.map((o) => PerpetualDataHandler.fromSmartContratOrderToClientOrder(o));
160
+ } else if (orders.length == 2) {
161
+ // first order is parent, second a child
162
+ obOrders[0] = PerpetualDataHandler.fromSmartContratOrderToClientOrder(orders[0], [ids[1], ZERO_ORDER_ID]);
163
+ obOrders[1] = PerpetualDataHandler.fromSmartContratOrderToClientOrder(orders[1], [ZERO_ORDER_ID, ids[0]]);
164
+ } else {
165
+ // first order is parent, other two its children
166
+ obOrders[0] = PerpetualDataHandler.fromSmartContratOrderToClientOrder(orders[0], [ids[1], ids[2]]);
167
+ obOrders[1] = PerpetualDataHandler.fromSmartContratOrderToClientOrder(orders[1], [ZERO_ORDER_ID, ids[0]]);
168
+ obOrders[2] = PerpetualDataHandler.fromSmartContratOrderToClientOrder(orders[2], [ZERO_ORDER_ID, ids[0]]);
169
+ }
170
+ return obOrders;
171
+ }
172
+ }