@d8x/perpetuals-sdk 0.0.45 → 0.0.46

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 (40) hide show
  1. package/README.md +12 -0
  2. package/abi/MockTokenSwap.json +186 -0
  3. package/config/defaultConfig.json +36 -10
  4. package/config/mockSwap.json +4 -0
  5. package/config/oldConfig.json +2 -1
  6. package/config/priceFeedConfig.json +18 -0
  7. package/dist/accountTrade.d.ts +1 -0
  8. package/dist/accountTrade.js +31 -7
  9. package/dist/d8XMath.d.ts +6 -0
  10. package/dist/d8XMath.js +19 -1
  11. package/dist/liquidatorTool.d.ts +6 -4
  12. package/dist/liquidatorTool.js +13 -7
  13. package/dist/marketData.d.ts +6 -11
  14. package/dist/marketData.js +59 -41
  15. package/dist/nodeSDKTypes.d.ts +33 -2
  16. package/dist/nodeSDKTypes.js +3 -18
  17. package/dist/orderReferrerTool.d.ts +17 -7
  18. package/dist/orderReferrerTool.js +43 -13
  19. package/dist/perpetualDataHandler.d.ts +46 -6
  20. package/dist/perpetualDataHandler.js +143 -16
  21. package/dist/perpetualEventHandler.d.ts +0 -3
  22. package/dist/perpetualEventHandler.js +0 -3
  23. package/dist/priceFeeds.d.ts +115 -0
  24. package/dist/priceFeeds.js +373 -0
  25. package/dist/triangulator.d.ts +27 -0
  26. package/dist/triangulator.js +107 -0
  27. package/dist/version.d.ts +1 -1
  28. package/dist/version.js +1 -1
  29. package/package.json +1 -1
  30. package/src/accountTrade.ts +31 -8
  31. package/src/d8XMath.ts +18 -0
  32. package/src/liquidatorTool.ts +28 -8
  33. package/src/marketData.ts +66 -41
  34. package/src/nodeSDKTypes.ts +30 -3
  35. package/src/orderReferrerTool.ts +56 -13
  36. package/src/perpetualDataHandler.ts +149 -21
  37. package/src/perpetualEventHandler.ts +0 -3
  38. package/src/priceFeeds.ts +371 -0
  39. package/src/triangulator.ts +105 -0
  40. package/src/version.ts +1 -1
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const ethers_1 = require("ethers");
16
+ const triangulator_1 = __importDefault(require("./triangulator"));
17
+ const d8XMath_1 = require("./d8XMath");
18
+ /**
19
+ * This class communicates with the REST API that provides price-data that is
20
+ * to be submitted to the smart contracts for certain functions such as
21
+ * trader liquidations, trade executions, change of trader margin amount.
22
+ */
23
+ class PriceFeeds {
24
+ constructor(dataHandler, priceFeedConfigNetwork) {
25
+ this.THRESHOLD_MARKET_CLOSED_SEC = 15; // smallest lag for which we consider the market as being closed
26
+ let configs = require("../config/priceFeedConfig.json");
27
+ this.config = PriceFeeds._selectConfig(configs, priceFeedConfigNetwork);
28
+ [this.feedInfo, this.feedEndpoints] = PriceFeeds._constructFeedInfo(this.config);
29
+ this.dataHandler = dataHandler;
30
+ this.triangulations = new Map();
31
+ }
32
+ /**
33
+ * Pre-processing of triangulations for symbols, given the price feeds
34
+ * @param symbols set of symbols we want to triangulate from price feeds
35
+ */
36
+ initializeTriangulations(symbols) {
37
+ let feedSymbols = new Array();
38
+ for (let [key, value] of this.feedInfo) {
39
+ feedSymbols.push(value.symbol);
40
+ }
41
+ for (let symbol of symbols.values()) {
42
+ let triangulation = triangulator_1.default.triangulate(feedSymbols, symbol);
43
+ this.triangulations.set(symbol, triangulation);
44
+ }
45
+ }
46
+ /**
47
+ * Get required information to be able to submit a blockchain transaction with price-update
48
+ * such as trade execution, liquidation
49
+ * @param symbol symbol of perpetual, e.g., BTC-USD-MATIC
50
+ * @returns PriceFeedSubmission, index prices, market closed information
51
+ */
52
+ fetchFeedPriceInfoAndIndicesForPerpetual(symbol) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ let indexSymbols = this.dataHandler.getIndexSymbols(symbol);
55
+ // fetch prices from required price-feeds (REST)
56
+ let submission = yield this.fetchLatestFeedPriceInfoForPerpetual(symbol);
57
+ // calculate index-prices from price-feeds
58
+ let [_idxPrices, _mktClosed] = this.calculateTriangulatedPricesFromFeedInfo(indexSymbols.filter((x) => x != ''), submission);
59
+ let idxPrices = [_idxPrices[0], 0];
60
+ let mktClosed = [_mktClosed[0], false];
61
+ if (idxPrices.length > 1) {
62
+ idxPrices[1] = _idxPrices[1];
63
+ mktClosed[1] = _mktClosed[1];
64
+ }
65
+ return { submission: submission, pxS2S3: idxPrices, mktClosed: mktClosed };
66
+ });
67
+ }
68
+ /**
69
+ * Get all prices/isMarketClosed for the provided symbols via
70
+ * "latest_price_feeds" and triangulation. Triangulation must be defined in config, unless
71
+ * it is a direct price feed.
72
+ * @returns map of feed-price symbol to price/isMarketClosed
73
+ */
74
+ fetchPrices(symbols) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ let feedPrices = yield this.fetchAllFeedPrices();
77
+ let [prices, mktClosed] = this.triangulatePricesFromFeedPrices(symbols, feedPrices);
78
+ let symMap = new Map;
79
+ for (let k = 0; k < symbols.length; k++) {
80
+ symMap.set(symbols[k], [prices[k], mktClosed[k]]);
81
+ }
82
+ return symMap;
83
+ });
84
+ }
85
+ /**
86
+ * Get index prices and market closed information for the given perpetual
87
+ * @param symbol perpetual symbol such as ETH-USD-MATIC
88
+ * @returns
89
+ */
90
+ fetchPricesForPerpetual(symbol) {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ let indexSymbols = this.dataHandler.getIndexSymbols(symbol).filter(x => x != "");
93
+ // determine relevant price feeds
94
+ let feedSymbols = new Array();
95
+ for (let sym of indexSymbols) {
96
+ if (sym != "") {
97
+ let triang = this.triangulations.get(sym);
98
+ if (triang == undefined) {
99
+ // no triangulation defined, so symbol must be a feed (unless misconfigured)
100
+ feedSymbols.push(sym);
101
+ }
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 = yield 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
+ fetchFeedPrices(symbols) {
128
+ return __awaiter(this, void 0, void 0, function* () {
129
+ let queries = new Array(this.feedEndpoints.length);
130
+ let symbolsOfEndpoint = [];
131
+ for (let j = 0; j < queries.length; j++) {
132
+ symbolsOfEndpoint.push([]);
133
+ }
134
+ for (let k = 0; k < this.config.ids.length; k++) {
135
+ let currFeed = this.config.ids[k];
136
+ if (symbols != undefined && !symbols.includes(currFeed.symbol)) {
137
+ continue;
138
+ }
139
+ // feedInfo: Map<string, {symbol:string, endpointId: number}>; // priceFeedId -> symbol, endpointId
140
+ let endpointId = this.feedInfo.get(currFeed.id).endpointId;
141
+ symbolsOfEndpoint[endpointId].push(currFeed.symbol);
142
+ if (queries[endpointId] == undefined) {
143
+ // each id can have a different endpoint, but we cluster
144
+ // the queries into one per endpoint
145
+ queries[endpointId] = this.feedEndpoints[endpointId] + "/latest_price_feeds?";
146
+ }
147
+ queries[endpointId] = queries[endpointId] + "ids[]=" + currFeed.id + "&";
148
+ }
149
+ let resultPrices = new Map();
150
+ for (let k = 0; k < queries.length; k++) {
151
+ if (queries[k] == undefined) {
152
+ continue;
153
+ }
154
+ let [id, pxInfo] = yield this.fetchPriceQuery(queries[k]);
155
+ let tsSecNow = Math.round(Date.now() / 1000);
156
+ for (let j = 0; j < pxInfo.length; j++) {
157
+ let price = (0, d8XMath_1.decNToFloat)(ethers_1.BigNumber.from(pxInfo[j].price), -pxInfo[j].expo);
158
+ let isMarketClosed = tsSecNow - pxInfo[j].publish_time > this.THRESHOLD_MARKET_CLOSED_SEC;
159
+ resultPrices.set(symbolsOfEndpoint[k][j], [price, isMarketClosed]);
160
+ }
161
+ }
162
+ return resultPrices;
163
+ });
164
+ }
165
+ /**
166
+ * Get all configured feed prices via "latest_price_feeds"
167
+ * @returns map of feed-price symbol to price/isMarketClosed
168
+ */
169
+ fetchAllFeedPrices() {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ return this.fetchFeedPrices();
172
+ });
173
+ }
174
+ /**
175
+ * Get the latest prices for a given perpetual from the offchain oracle
176
+ * networks
177
+ * @param symbol perpetual symbol of the form BTC-USD-MATIC
178
+ * @returns array of price feed updates that can be submitted to the smart contract
179
+ * and corresponding price information
180
+ */
181
+ fetchLatestFeedPriceInfoForPerpetual(symbol) {
182
+ return __awaiter(this, void 0, void 0, function* () {
183
+ let feedIds = this.dataHandler.getPriceIds(symbol);
184
+ let queries = new Array(this.feedEndpoints.length);
185
+ // we need to preserve the order of the price feeds
186
+ let orderEndpointNumber = new Array();
187
+ // count how many prices per endpoint
188
+ let idCountPriceFeeds = new Array(this.feedEndpoints.length);
189
+ let symbols = new Array();
190
+ for (let k = 0; k < feedIds.length; k++) {
191
+ let info = this.feedInfo.get(feedIds[k]);
192
+ if (info == undefined) {
193
+ throw new Error(`priceFeeds: config for symbol ${symbol} insufficient`);
194
+ }
195
+ let id = info.endpointId;
196
+ symbols.push(info.symbol);
197
+ if (queries[id] == undefined) {
198
+ // each id can have a different endpoint, but we cluster
199
+ // the queries into one per endpoint
200
+ queries[id] = this.feedEndpoints[id] + "/latest_vaas_px?";
201
+ idCountPriceFeeds[id] = 0;
202
+ }
203
+ queries[id] = queries[id] + "ids[]=" + feedIds[k] + "&";
204
+ orderEndpointNumber.push(id * 100 + idCountPriceFeeds[id]);
205
+ idCountPriceFeeds[id] = idCountPriceFeeds[id] + 1;
206
+ }
207
+ let data = yield Promise.all(queries.map((q) => __awaiter(this, void 0, void 0, function* () {
208
+ if (q != undefined) {
209
+ return this.fetchVAAQuery(q);
210
+ }
211
+ else {
212
+ return [[], []];
213
+ }
214
+ })));
215
+ // re-order arrays so we preserve the order of the feeds
216
+ const priceFeedUpdates = new Array();
217
+ const prices = new Array();
218
+ const mktClosed = new Array();
219
+ const timestamps = new Array();
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 = data[endpointId][1][idWithinEndpoint];
226
+ let price = (0, d8XMath_1.decNToFloat)(ethers_1.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
+ return { "symbols": symbols, priceFeedVaas: priceFeedUpdates, prices: prices, isMarketClosed: mktClosed, timestamps: timestamps };
233
+ });
234
+ }
235
+ /**
236
+ * Extract pair-prices from underlying price feeds via triangulation
237
+ * The function either needs a direct price feed or a defined triangulation to succesfully
238
+ * return a triangulated price
239
+ * @param symbols array of pairs for which we want prices, e.g., [BTC-USDC, ETH-USD]
240
+ * @param feeds data obtained via fetchLatestFeedPriceInfo or fetchLatestFeedPrices
241
+ * @returns array of prices with same order as symbols
242
+ */
243
+ calculateTriangulatedPricesFromFeedInfo(symbols, feeds) {
244
+ let priceMap = new Map();
245
+ for (let j = 0; j < feeds.prices.length; j++) {
246
+ priceMap.set(feeds.symbols[j], [feeds.prices[j], feeds.isMarketClosed[j]]);
247
+ }
248
+ return this.triangulatePricesFromFeedPrices(symbols, priceMap);
249
+ }
250
+ /**
251
+ * Extract pair-prices from underlying price feeds via triangulation
252
+ * The function either needs a direct price feed or a defined triangulation to succesfully
253
+ * return a triangulated price
254
+ * @param symbols array of pairs for which we want prices, e.g., [BTC-USDC, ETH-USD]
255
+ * @param feeds data obtained via fetchLatestFeedPriceInfo or fetchLatestFeedPrices
256
+ * @returns array of prices with same order as symbols
257
+ */
258
+ triangulatePricesFromFeedPrices(symbols, feedPriceMap) {
259
+ let prices = new Array();
260
+ let mktClosed = new Array();
261
+ for (let k = 0; k < symbols.length; k++) {
262
+ let triangulation = this.triangulations.get(symbols[k]);
263
+ if (triangulation == undefined) {
264
+ let feedPrice = feedPriceMap.get(symbols[k]);
265
+ if (feedPrice == undefined) {
266
+ throw new Error(`PriceFeeds: no triangulation defined for ${symbols[k]}`);
267
+ }
268
+ else {
269
+ prices.push(feedPrice[0]); //price
270
+ mktClosed.push(feedPrice[1]); //market closed?
271
+ continue;
272
+ }
273
+ }
274
+ let [px, isMktClosed] = triangulator_1.default.calculateTriangulatedPrice(triangulation, feedPriceMap);
275
+ prices.push(px);
276
+ mktClosed.push(isMktClosed);
277
+ }
278
+ return [prices, mktClosed];
279
+ }
280
+ /**
281
+ * Queries the REST endpoint and returns parsed VAA price data
282
+ * @param query query price-info from endpoint
283
+ * @returns vaa and price info
284
+ */
285
+ fetchVAAQuery(query) {
286
+ return __awaiter(this, void 0, void 0, function* () {
287
+ const headers = { headers: { 'Content-Type': 'application/json' } };
288
+ let response = yield fetch(query, headers);
289
+ if (!response.ok) {
290
+ throw new Error(`Failed to fetch posts (${response.status}): ${response.statusText}`);
291
+ }
292
+ let values = (yield response.json());
293
+ const priceFeedUpdates = new Array();
294
+ const px = new Array();
295
+ for (let k = 0; k < values.length; k++) {
296
+ priceFeedUpdates.push("0x" + Buffer.from(values[k][0], "base64").toString("hex"));
297
+ px.push(values[k][1]);
298
+ }
299
+ return [priceFeedUpdates, px];
300
+ });
301
+ }
302
+ /**
303
+ * Queries the REST endpoint and returns parsed price data
304
+ * @param query query price-info from endpoint
305
+ * @returns vaa and price info
306
+ */
307
+ fetchPriceQuery(query) {
308
+ return __awaiter(this, void 0, void 0, function* () {
309
+ const headers = { headers: { 'Content-Type': 'application/json' } };
310
+ let response = yield fetch(query, headers);
311
+ if (!response.ok) {
312
+ throw new Error(`Failed to fetch posts (${response.status}): ${response.statusText}`);
313
+ }
314
+ let values = yield response.json();
315
+ const priceFeedUpdates = new Array();
316
+ const px = new Array();
317
+ for (let k = 0; k < values.length; k++) {
318
+ priceFeedUpdates.push("0x" + Buffer.from(values[k].id, "base64").toString("hex"));
319
+ px.push(values[k].price);
320
+ }
321
+ return [priceFeedUpdates, px];
322
+ });
323
+ }
324
+ /**
325
+ * Searches for configuration for given network
326
+ * @param configs pricefeed configuration from json
327
+ * @param network e.g. testnet
328
+ * @returns selected configuration
329
+ */
330
+ static _selectConfig(configs, network) {
331
+ let k = 0;
332
+ while (k < configs.length) {
333
+ if (configs[k].network == network) {
334
+ return configs[k];
335
+ }
336
+ k = k + 1;
337
+ }
338
+ throw new Error(`PriceFeeds: config not found for network ${network}`);
339
+ }
340
+ /**
341
+ * Wraps configuration into convenient data-structure
342
+ * @param config configuration for the selected network
343
+ * @returns feedInfo-map and endPoints-array
344
+ */
345
+ static _constructFeedInfo(config) {
346
+ let feed = new Map();
347
+ let endpointId = -1;
348
+ let type = "";
349
+ let feedEndpoints = new Array();
350
+ for (let k = 0; k < config.endpoints.length; k++) {
351
+ feedEndpoints.push(config.endpoints[k].endpoint);
352
+ }
353
+ for (let k = 0; k < config.ids.length; k++) {
354
+ if (type != config.ids[k].type) {
355
+ type = config.ids[k].type;
356
+ let j = 0;
357
+ while (j < config.endpoints.length) {
358
+ if (config.endpoints[j].type == type) {
359
+ endpointId = j;
360
+ j = config.endpoints.length;
361
+ }
362
+ j++;
363
+ }
364
+ if (config.endpoints[endpointId].type != type) {
365
+ throw new Error(`priceFeeds: no enpoint found for ${type} check priceFeedConfig`);
366
+ }
367
+ }
368
+ feed.set(config.ids[k].id, { symbol: config.ids[k].symbol.toUpperCase(), endpointId: endpointId });
369
+ }
370
+ return [feed, feedEndpoints];
371
+ }
372
+ }
373
+ exports.default = PriceFeeds;
@@ -0,0 +1,27 @@
1
+ export default class Triangulator {
2
+ /**
3
+ * Find all possible triangulation paths
4
+ * @param ccyBase array of base currencies ['BTC', 'ETH', 'MATIC', ...]
5
+ * @param ccyQuote array of quote currencies corresponding to base ['USD', 'USD', ...]
6
+ * @param pair pair we want to calculate, e.g. BTC-USDC
7
+ * @returns array of different paths for which multiplication leads to
8
+ * desired pair, e.g. [['MATIC-USD'], ['MATIC-ETH', 'ETH-USD']]
9
+ */
10
+ static findPath(ccyBase: string[], ccyQuote: string[], pair: string): Array<Array<string>>;
11
+ /**
12
+ * Calculate the triangulated price from underlying prices
13
+ * @param triangulation triangulation of with symbol and is-inverted flag
14
+ * @param feedIdxPrices map of symbol to price
15
+ * @returns triangulated price (scalar), true if market closed or price unavailable
16
+ */
17
+ static calculateTriangulatedPrice(triangulation: [string[], boolean[]], feedIdxPrices: Map<string, [number, boolean]>): [number, boolean];
18
+ /**
19
+ * Finds shortest path and returns indices required and whether to divide or not
20
+ * @example triangulate BTC-USDC: [ [ 'BTC-USD', 'USDC-USD' ], [ false, true ] ]
21
+ * @param ccyArr array of available indices (e.g. from websocket)
22
+ * @param symbol symbol we are looking for (e.g. MATIC-USDC)
23
+ * @returns shortest path with given indices, array whether we have to divide (true) or multiply
24
+ * to arrive at the desired price
25
+ */
26
+ static triangulate(ccyArr: string[], symbol: string): [string[], boolean[]];
27
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class Triangulator {
4
+ /**
5
+ * Find all possible triangulation paths
6
+ * @param ccyBase array of base currencies ['BTC', 'ETH', 'MATIC', ...]
7
+ * @param ccyQuote array of quote currencies corresponding to base ['USD', 'USD', ...]
8
+ * @param pair pair we want to calculate, e.g. BTC-USDC
9
+ * @returns array of different paths for which multiplication leads to
10
+ * desired pair, e.g. [['MATIC-USD'], ['MATIC-ETH', 'ETH-USD']]
11
+ */
12
+ static findPath(ccyBase, ccyQuote, pair) {
13
+ let [base, quote] = pair.split("-");
14
+ let paths = new Array();
15
+ for (let k = 0; k < ccyBase.length; k++) {
16
+ let currentPath = [];
17
+ if (ccyBase[k] == base) {
18
+ currentPath.push(ccyBase[k] + "-" + ccyQuote[k]);
19
+ if (ccyQuote[k] == quote) {
20
+ // we are done
21
+ paths.push(currentPath);
22
+ }
23
+ else {
24
+ // find path for ccyQuote[k]-quote without the currency
25
+ let newBase = new Array();
26
+ let newQuote = new Array();
27
+ for (let j = 0; j < ccyBase.length; j++) {
28
+ if (j != k && ccyQuote[j] + "-" + ccyBase[j] != ccyBase[k] + "-" + ccyQuote[k]) {
29
+ newBase.push(ccyBase[j]);
30
+ newQuote.push(ccyQuote[j]);
31
+ }
32
+ }
33
+ let recPaths = Triangulator.findPath(newBase, newQuote, ccyQuote[k] + "-" + quote);
34
+ for (let j = 0; j < recPaths.length; j++) {
35
+ paths.push(currentPath.concat(recPaths[j]));
36
+ }
37
+ }
38
+ }
39
+ }
40
+ return paths;
41
+ }
42
+ /**
43
+ * Calculate the triangulated price from underlying prices
44
+ * @param triangulation triangulation of with symbol and is-inverted flag
45
+ * @param feedIdxPrices map of symbol to price
46
+ * @returns triangulated price (scalar), true if market closed or price unavailable
47
+ */
48
+ static calculateTriangulatedPrice(triangulation, feedIdxPrices) {
49
+ let px = 1;
50
+ let isOpen = true;
51
+ for (let j = 0; j < triangulation[0].length; j++) {
52
+ let pxFeed = feedIdxPrices.get(triangulation[0][j]);
53
+ if (pxFeed == undefined) {
54
+ // no price available for given index
55
+ return [-1, true];
56
+ }
57
+ px = triangulation[1][j] ? px / pxFeed[0] : px * pxFeed[0];
58
+ let isClosed = pxFeed[1];
59
+ isOpen = isOpen && !isClosed;
60
+ }
61
+ return [px, !isOpen];
62
+ }
63
+ /**
64
+ * Finds shortest path and returns indices required and whether to divide or not
65
+ * @example triangulate BTC-USDC: [ [ 'BTC-USD', 'USDC-USD' ], [ false, true ] ]
66
+ * @param ccyArr array of available indices (e.g. from websocket)
67
+ * @param symbol symbol we are looking for (e.g. MATIC-USDC)
68
+ * @returns shortest path with given indices, array whether we have to divide (true) or multiply
69
+ * to arrive at the desired price
70
+ */
71
+ static triangulate(ccyArr, symbol) {
72
+ let ccyBase = ccyArr.map((x) => x.split("-")[0]);
73
+ let ccyQuote = ccyArr.map((x) => x.split("-")[1]);
74
+ let p = Triangulator.findPath(ccyBase.concat(ccyQuote), ccyQuote.concat(ccyBase), symbol);
75
+ if (p.length == 0) {
76
+ return [[], []];
77
+ }
78
+ let len = p.map((x) => x.length);
79
+ let minLen = 100;
80
+ let minIdx = -1;
81
+ for (let j = 0; j < p.length; j++) {
82
+ if (len[j] < minLen) {
83
+ minLen = len[j];
84
+ minIdx = j;
85
+ if (minLen == 1) {
86
+ break;
87
+ }
88
+ }
89
+ }
90
+ let path = p[minIdx];
91
+ let invert = new Array();
92
+ let indexPath = new Array();
93
+ for (let k = 0; k < path.length; k++) {
94
+ if (ccyArr.includes(path[k], 0)) {
95
+ indexPath.push(path[k]);
96
+ invert.push(false);
97
+ }
98
+ else {
99
+ let inv = path[k].split("-");
100
+ indexPath.push(inv[1] + "-" + inv[0]);
101
+ invert.push(true);
102
+ }
103
+ }
104
+ return [indexPath, invert];
105
+ }
106
+ }
107
+ exports.default = Triangulator;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const D8X_SDK_VERSION = "0.0.45";
1
+ export declare const D8X_SDK_VERSION = "0.0.46";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.D8X_SDK_VERSION = void 0;
4
- exports.D8X_SDK_VERSION = "0.0.45";
4
+ exports.D8X_SDK_VERSION = "0.0.46";
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "name": "@d8x/perpetuals-sdk",
35
35
  "description": "Node TypeScript SDK for D8X Perpetual Futures",
36
- "version": "0.0.45",
36
+ "version": "0.0.46",
37
37
  "main": "./dist/index.js",
38
38
  "types": "./dist/index.d.ts",
39
39
  "directories": {
@@ -2,12 +2,14 @@ import { ethers } from "ethers";
2
2
  import { ABK64x64ToFloat, floatToABK64x64 } from "./d8XMath";
3
3
  import MarketData from "./marketData";
4
4
  import {
5
+ MOCK_TOKEN_SWAP_ABI,
5
6
  NodeSDKConfig,
6
7
  Order,
7
8
  OrderResponse,
8
9
  PerpetualStaticInfo,
9
10
  SmartContractOrder,
10
11
  ZERO_ADDRESS,
12
+ PriceFeedSubmission,
11
13
  } from "./nodeSDKTypes";
12
14
  import PerpetualDataHandler from "./perpetualDataHandler";
13
15
  import TraderDigests from "./traderDigests";
@@ -78,12 +80,6 @@ export default class AccountTrade extends WriteAccessHandler {
78
80
  return await this._cancelOrder(symbol, orderId, orderBookContract);
79
81
  }
80
82
 
81
- /*
82
- TODO: -deposit (margin into account)
83
- -withdraw margin withdraw(uint24 _iPerpetualId, int128 _fAmount)
84
-
85
- */
86
-
87
83
  /**
88
84
  * Submits an order to the exchange.
89
85
  * @param {Order} order Order structure. As a minimum the structure needs to
@@ -339,7 +335,11 @@ export default class AccountTrade extends WriteAccessHandler {
339
335
  }
340
336
  let perpId = this.getPerpIdFromSymbol(symbol);
341
337
  let fAmountCC = floatToABK64x64(amount);
342
- return await this.proxyContract.deposit(perpId, fAmountCC);
338
+ let priceFeedData: PriceFeedSubmission = await this.fetchLatestFeedPriceInfo(symbol);
339
+ return await this.proxyContract.deposit(perpId, fAmountCC, priceFeedData.priceFeedVaas, priceFeedData.timestamps, {
340
+ gasLimit: this.gasLimit,
341
+ value: this.PRICE_UPDATE_FEE_GWEI * priceFeedData.priceFeedVaas.length,
342
+ });
343
343
  }
344
344
 
345
345
  /**
@@ -353,6 +353,29 @@ export default class AccountTrade extends WriteAccessHandler {
353
353
  }
354
354
  let perpId = this.getPerpIdFromSymbol(symbol);
355
355
  let fAmountCC = floatToABK64x64(amount);
356
- return await this.proxyContract.withdraw(perpId, fAmountCC);
356
+ let priceFeedData: PriceFeedSubmission = await this.fetchLatestFeedPriceInfo(symbol);
357
+ return await this.proxyContract.withdraw(perpId, fAmountCC, priceFeedData.priceFeedVaas, priceFeedData.timestamps, {
358
+ gasLimit: this.gasLimit,
359
+ value: this.PRICE_UPDATE_FEE_GWEI * priceFeedData.priceFeedVaas.length,
360
+ });
361
+ }
362
+
363
+ public async swapForMockToken(symbol: string, amountToPay: string) {
364
+ if (this.signer == null) {
365
+ throw Error("no wallet initialized. Use createProxyInstance().");
366
+ }
367
+ let tokenAddress = this.getMarginTokenFromSymbol(symbol);
368
+ if (tokenAddress == undefined) {
369
+ throw Error("symbols not found");
370
+ }
371
+ let tokenToSwap = new Map<string, string>(Object.entries(require("../config/mockSwap.json")));
372
+ let swapAddress = tokenToSwap.get(tokenAddress);
373
+ if (swapAddress == undefined) {
374
+ throw Error("No swap contract found for symbol.");
375
+ }
376
+ let contract = new ethers.Contract(swapAddress, MOCK_TOKEN_SWAP_ABI, this.signer.provider);
377
+ return await contract.swapToMockToken({
378
+ value: ethers.utils.parseEther(amountToPay),
379
+ });
357
380
  }
358
381
  }
package/src/d8XMath.ts CHANGED
@@ -28,6 +28,24 @@ export function ABK64x64ToFloat(x: BigNumber | number): number {
28
28
  return parseFloat(NumberStr) * s;
29
29
  }
30
30
 
31
+ /**
32
+ *
33
+ * @param {BigNumber} x BigNumber in Dec-N format
34
+ * @returns {number} x as a float (number)
35
+ */
36
+ export function decNToFloat(x: BigNumber, numDec: number) {
37
+ //x: BigNumber in DecN format to float
38
+ const DECIMALS = BigNumber.from(10).pow(BigNumber.from(numDec));
39
+ let s = x.lt(0) ? -1 : 1;
40
+ x = x.mul(s);
41
+ let xInt = x.div(DECIMALS);
42
+ let xDec = x.sub(xInt.mul(DECIMALS));
43
+ let k = numDec - xDec.toString().length;
44
+ let sPad = "0".repeat(k);
45
+ let NumberStr = xInt.toString() + "." + sPad + xDec.toString();
46
+ return parseFloat(NumberStr) * s;
47
+ }
48
+
31
49
  /**
32
50
  *
33
51
  * @param {BigNumber} x BigNumber in Dec18 format