@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.
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +2 -2
- package/dist/cjs/version.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/package.json +3 -2
- package/src/abi/ERC20.json +288 -0
- package/src/abi/IPerpetualManager.json +5888 -0
- package/src/abi/LimitOrderBook.json +1062 -0
- package/src/abi/LimitOrderBookFactory.json +161 -0
- package/src/abi/MockTokenSwap.json +186 -0
- package/src/abi/ShareToken.json +428 -0
- package/src/accountTrade.ts +428 -0
- package/src/brokerTool.ts +555 -0
- package/src/config/defaultConfig.json +62 -0
- package/src/config/mockSwap.json +6 -0
- package/src/config/priceFeedConfig.json +104 -0
- package/src/config/symbolList.json +13 -0
- package/src/contracts/ERC20.ts +444 -0
- package/src/contracts/IPerpetualManager.ts +7227 -0
- package/src/contracts/LimitOrderBook.ts +1251 -0
- package/src/contracts/LimitOrderBookFactory.ts +348 -0
- package/src/contracts/MockTokenSwap.ts +373 -0
- package/src/contracts/ShareToken.ts +695 -0
- package/src/contracts/common.ts +44 -0
- package/src/contracts/factories/ERC20__factory.ts +306 -0
- package/src/contracts/factories/IPerpetualManager__factory.ts +5912 -0
- package/src/contracts/factories/LimitOrderBookFactory__factory.ts +189 -0
- package/src/contracts/factories/LimitOrderBook__factory.ts +1086 -0
- package/src/contracts/factories/MockTokenSwap__factory.ts +207 -0
- package/src/contracts/factories/ShareToken__factory.ts +449 -0
- package/src/contracts/factories/index.ts +9 -0
- package/src/contracts/index.ts +16 -0
- package/src/d8XMath.ts +376 -0
- package/src/index.ts +29 -0
- package/src/liquidatorTool.ts +270 -0
- package/src/liquidityProviderTool.ts +148 -0
- package/src/marketData.ts +1310 -0
- package/src/nodeSDKTypes.ts +332 -0
- package/src/orderReferrerTool.ts +516 -0
- package/src/perpetualDataHandler.ts +1161 -0
- package/src/perpetualEventHandler.ts +455 -0
- package/src/priceFeeds.ts +382 -0
- package/src/traderDigests.ts +86 -0
- package/src/traderInterface.ts +172 -0
- package/src/triangulator.ts +105 -0
- package/src/utils.ts +134 -0
- package/src/version.ts +1 -0
- 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
|
+
}
|