@drift-labs/sdk 0.2.0-master.31 → 0.2.0-master.33
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/lib/accounts/bulkAccountLoader.js +2 -1
- package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +2 -2
- package/lib/accounts/pollingClearingHouseAccountSubscriber.js +8 -8
- package/lib/accounts/types.d.ts +1 -0
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +2 -2
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +7 -7
- package/lib/addresses/marketAddresses.js +1 -1
- package/lib/addresses/pda.d.ts +4 -4
- package/lib/addresses/pda.js +23 -22
- package/lib/admin.d.ts +30 -32
- package/lib/admin.js +111 -119
- package/lib/clearingHouse.d.ts +43 -34
- package/lib/clearingHouse.js +353 -193
- package/lib/clearingHouseConfig.d.ts +2 -2
- package/lib/clearingHouseUser.d.ts +26 -5
- package/lib/clearingHouseUser.js +151 -51
- package/lib/config.d.ts +2 -0
- package/lib/config.js +5 -1
- package/lib/constants/numericConstants.d.ts +1 -0
- package/lib/constants/numericConstants.js +3 -2
- package/lib/dlob/DLOB.d.ts +19 -11
- package/lib/dlob/DLOB.js +208 -107
- package/lib/dlob/DLOBNode.js +2 -10
- package/lib/dlob/NodeList.d.ts +1 -0
- package/lib/dlob/NodeList.js +6 -1
- package/lib/events/eventSubscriber.d.ts +1 -0
- package/lib/events/eventSubscriber.js +11 -4
- package/lib/events/fetchLogs.d.ts +3 -1
- package/lib/events/fetchLogs.js +13 -5
- package/lib/events/pollingLogProvider.js +1 -1
- package/lib/events/types.d.ts +1 -1
- package/lib/events/webSocketLogProvider.js +1 -1
- package/lib/factory/bigNum.d.ts +5 -4
- package/lib/factory/bigNum.js +36 -6
- package/lib/idl/clearing_house.json +1527 -1242
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/math/amm.js +9 -9
- package/lib/math/exchangeStatus.d.ts +4 -0
- package/lib/math/exchangeStatus.js +18 -0
- package/lib/math/funding.js +10 -10
- package/lib/math/margin.js +6 -1
- package/lib/math/market.js +9 -9
- package/lib/math/orders.d.ts +7 -3
- package/lib/math/orders.js +39 -31
- package/lib/math/repeg.js +3 -3
- package/lib/math/spotBalance.js +3 -3
- package/lib/math/spotPosition.js +2 -2
- package/lib/serum/serumFulfillmentConfigMap.d.ts +10 -0
- package/lib/serum/serumFulfillmentConfigMap.js +17 -0
- package/lib/serum/serumSubscriber.d.ts +4 -0
- package/lib/serum/serumSubscriber.js +16 -1
- package/lib/types.d.ts +70 -85
- package/lib/types.js +12 -11
- package/lib/userMap/userMap.d.ts +17 -1
- package/lib/userMap/userMap.js +12 -0
- package/lib/userName.d.ts +1 -0
- package/lib/userName.js +3 -2
- package/package.json +1 -1
- package/src/accounts/bulkAccountLoader.ts +5 -1
- package/src/accounts/pollingClearingHouseAccountSubscriber.ts +9 -9
- package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +8 -8
- package/src/addresses/marketAddresses.ts +2 -2
- package/src/addresses/pda.ts +20 -20
- package/src/admin.ts +246 -221
- package/src/clearingHouse.ts +556 -236
- package/src/clearingHouseConfig.ts +2 -2
- package/src/clearingHouseUser.ts +237 -87
- package/src/config.ts +8 -1
- package/src/constants/numericConstants.ts +5 -1
- package/src/dlob/DLOB.ts +290 -120
- package/src/dlob/DLOBNode.ts +2 -14
- package/src/dlob/NodeList.ts +7 -1
- package/src/events/eventSubscriber.ts +18 -4
- package/src/events/fetchLogs.ts +20 -5
- package/src/events/pollingLogProvider.ts +1 -1
- package/src/events/types.ts +2 -1
- package/src/events/webSocketLogProvider.ts +1 -1
- package/src/factory/bigNum.ts +59 -6
- package/src/idl/clearing_house.json +1527 -1242
- package/src/idl/pyth.json +98 -2
- package/src/index.ts +3 -0
- package/src/math/amm.ts +9 -9
- package/src/math/exchangeStatus.ts +31 -0
- package/src/math/funding.ts +20 -10
- package/src/math/margin.ts +7 -1
- package/src/math/market.ts +9 -9
- package/src/math/orders.ts +44 -29
- package/src/math/repeg.ts +3 -3
- package/src/math/spotBalance.ts +4 -4
- package/src/math/spotPosition.ts +2 -2
- package/src/serum/serumFulfillmentConfigMap.ts +26 -0
- package/src/serum/serumSubscriber.ts +20 -1
- package/src/types.ts +75 -61
- package/src/userMap/userMap.ts +25 -1
- package/src/userName.ts +2 -1
- package/tests/bn/test.ts +22 -1
- package/tests/dlob/helpers.ts +252 -81
- package/tests/dlob/test.ts +1115 -215
package/src/dlob/DLOB.ts
CHANGED
|
@@ -12,17 +12,22 @@ import {
|
|
|
12
12
|
isVariant,
|
|
13
13
|
getVariant,
|
|
14
14
|
Order,
|
|
15
|
-
ZERO,
|
|
16
15
|
PRICE_PRECISION,
|
|
17
16
|
SpotMarketAccount,
|
|
18
17
|
PerpMarketAccount,
|
|
19
18
|
OraclePriceData,
|
|
20
19
|
SlotSubscriber,
|
|
21
|
-
|
|
20
|
+
UserMapInterface,
|
|
22
21
|
MarketTypeStr,
|
|
22
|
+
StateAccount,
|
|
23
|
+
isMarketOrder,
|
|
24
|
+
isLimitOrder,
|
|
25
|
+
hasLimitPrice,
|
|
26
|
+
getOptionalLimitPrice,
|
|
23
27
|
} from '..';
|
|
24
28
|
import { PublicKey } from '@solana/web3.js';
|
|
25
29
|
import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
|
|
30
|
+
import { ammPaused, exchangePaused, fillPaused } from '../math/exchangeStatus';
|
|
26
31
|
|
|
27
32
|
export type MarketNodeLists = {
|
|
28
33
|
limit: {
|
|
@@ -59,11 +64,13 @@ type Side = 'ask' | 'bid' | 'both' | 'nocross';
|
|
|
59
64
|
export class DLOB {
|
|
60
65
|
openOrders = new Map<MarketTypeStr, Set<string>>();
|
|
61
66
|
orderLists = new Map<MarketTypeStr, Map<number, MarketNodeLists>>();
|
|
67
|
+
stateAccount: StateAccount;
|
|
62
68
|
marketIndexToAccount = new Map<
|
|
63
69
|
MarketTypeStr,
|
|
64
70
|
Map<number, PerpMarketAccount | SpotMarketAccount>
|
|
65
71
|
>();
|
|
66
72
|
|
|
73
|
+
userMap: UserMapInterface;
|
|
67
74
|
silent = false;
|
|
68
75
|
initialized = false;
|
|
69
76
|
|
|
@@ -71,13 +78,18 @@ export class DLOB {
|
|
|
71
78
|
*
|
|
72
79
|
* @param perpMarkets The perp markets to maintain a DLOB for
|
|
73
80
|
* @param spotMarkets The spot markets to maintain a DLOB for
|
|
81
|
+
* @param userMap map of all users
|
|
74
82
|
* @param silent set to true to prevent logging on inserts and removals
|
|
75
83
|
*/
|
|
76
84
|
public constructor(
|
|
77
85
|
perpMarkets: PerpMarketAccount[],
|
|
78
86
|
spotMarkets: SpotMarketAccount[],
|
|
87
|
+
stateAccount: StateAccount,
|
|
88
|
+
userMap: UserMapInterface,
|
|
79
89
|
silent?: boolean
|
|
80
90
|
) {
|
|
91
|
+
this.stateAccount = stateAccount;
|
|
92
|
+
this.userMap = userMap;
|
|
81
93
|
this.silent = silent;
|
|
82
94
|
|
|
83
95
|
this.openOrders.set('perp', new Set<string>());
|
|
@@ -135,39 +147,49 @@ export class DLOB {
|
|
|
135
147
|
}
|
|
136
148
|
}
|
|
137
149
|
|
|
150
|
+
public clear() {
|
|
151
|
+
for (const marketType of this.openOrders.keys()) {
|
|
152
|
+
this.openOrders.get(marketType).clear();
|
|
153
|
+
}
|
|
154
|
+
this.openOrders.clear();
|
|
155
|
+
|
|
156
|
+
for (const marketType of this.orderLists.keys()) {
|
|
157
|
+
for (const marketIndex of this.orderLists.get(marketType).keys()) {
|
|
158
|
+
const marketNodeLists = this.orderLists
|
|
159
|
+
.get(marketType)
|
|
160
|
+
.get(marketIndex);
|
|
161
|
+
for (const side of Object.keys(marketNodeLists)) {
|
|
162
|
+
for (const orderType of Object.keys(marketNodeLists[side])) {
|
|
163
|
+
marketNodeLists[side][orderType].clear();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.orderLists.clear();
|
|
169
|
+
|
|
170
|
+
for (const marketType of this.marketIndexToAccount.keys()) {
|
|
171
|
+
this.marketIndexToAccount.get(marketType).clear();
|
|
172
|
+
}
|
|
173
|
+
this.marketIndexToAccount.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
138
176
|
/**
|
|
139
177
|
* initializes a new DLOB instance
|
|
140
178
|
*
|
|
141
|
-
* @param clearingHouse The ClearingHouse instance to use for price data
|
|
142
179
|
* @returns a promise that resolves when the DLOB is initialized
|
|
143
180
|
*/
|
|
144
|
-
public async init(
|
|
145
|
-
clearingHouse: ClearingHouse,
|
|
146
|
-
userMap?: UserMap
|
|
147
|
-
): Promise<boolean> {
|
|
181
|
+
public async init(): Promise<boolean> {
|
|
148
182
|
if (this.initialized) {
|
|
149
183
|
return false;
|
|
150
184
|
}
|
|
151
|
-
if (userMap) {
|
|
152
|
-
// initialize the dlob with the user map (prevents hitting getProgramAccounts)
|
|
153
|
-
for (const user of userMap.values()) {
|
|
154
|
-
const userAccount = user.getUserAccount();
|
|
155
|
-
const userAccountPubkey = user.getUserAccountPublicKey();
|
|
156
185
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
// @ts-ignore
|
|
165
|
-
const userAccount: UserAccount = programAccount.account;
|
|
166
|
-
const userAccountPublicKey = programAccount.publicKey;
|
|
167
|
-
|
|
168
|
-
for (const order of userAccount.orders) {
|
|
169
|
-
this.insertOrder(order, userAccountPublicKey);
|
|
170
|
-
}
|
|
186
|
+
// initialize the dlob with the user map (prevents hitting getProgramAccounts)
|
|
187
|
+
for (const user of this.userMap.values()) {
|
|
188
|
+
const userAccount = user.getUserAccount();
|
|
189
|
+
const userAccountPubkey = user.getUserAccountPublicKey();
|
|
190
|
+
|
|
191
|
+
for (const order of userAccount.orders) {
|
|
192
|
+
this.insertOrder(order, userAccountPubkey);
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
195
|
|
|
@@ -239,7 +261,7 @@ export class DLOB {
|
|
|
239
261
|
type = 'trigger';
|
|
240
262
|
} else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
|
|
241
263
|
type = 'market';
|
|
242
|
-
} else if (
|
|
264
|
+
} else if (order.oraclePriceOffset !== 0) {
|
|
243
265
|
type = 'floatingLimit';
|
|
244
266
|
} else {
|
|
245
267
|
type = 'limit';
|
|
@@ -265,12 +287,21 @@ export class DLOB {
|
|
|
265
287
|
|
|
266
288
|
public findNodesToFill(
|
|
267
289
|
marketIndex: number,
|
|
268
|
-
|
|
269
|
-
|
|
290
|
+
fallbackBid: BN | undefined,
|
|
291
|
+
fallbackAsk: BN | undefined,
|
|
270
292
|
slot: number,
|
|
293
|
+
ts: number,
|
|
271
294
|
marketType: MarketType,
|
|
272
295
|
oraclePriceData: OraclePriceData
|
|
273
296
|
): NodeToFill[] {
|
|
297
|
+
const marketAccount = this.marketIndexToAccount
|
|
298
|
+
.get(getVariant(marketType) as MarketTypeStr)
|
|
299
|
+
.get(marketIndex);
|
|
300
|
+
|
|
301
|
+
if (fillPaused(this.stateAccount, marketAccount)) {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
|
|
274
305
|
// Find all the crossing nodes
|
|
275
306
|
const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
|
|
276
307
|
marketIndex,
|
|
@@ -279,25 +310,27 @@ export class DLOB {
|
|
|
279
310
|
oraclePriceData
|
|
280
311
|
);
|
|
281
312
|
|
|
282
|
-
|
|
283
|
-
|
|
313
|
+
let fallbackCrossingNodesToFill = new Array<NodeToFill>();
|
|
314
|
+
if (!ammPaused(this.stateAccount, marketAccount)) {
|
|
315
|
+
fallbackCrossingNodesToFill = this.findFallbackCrossingNodesToFill(
|
|
284
316
|
marketIndex,
|
|
285
|
-
|
|
286
|
-
|
|
317
|
+
fallbackBid,
|
|
318
|
+
fallbackAsk,
|
|
287
319
|
slot,
|
|
288
320
|
marketType,
|
|
289
321
|
oraclePriceData
|
|
290
322
|
);
|
|
323
|
+
}
|
|
291
324
|
|
|
292
325
|
// get expired market nodes
|
|
293
|
-
const
|
|
326
|
+
const expiredNodesToFill = this.findExpiredNodesToFill(
|
|
294
327
|
marketIndex,
|
|
295
|
-
|
|
328
|
+
ts,
|
|
296
329
|
marketType
|
|
297
330
|
);
|
|
298
331
|
return crossingNodesToFill.concat(
|
|
299
|
-
|
|
300
|
-
|
|
332
|
+
fallbackCrossingNodesToFill,
|
|
333
|
+
expiredNodesToFill
|
|
301
334
|
);
|
|
302
335
|
}
|
|
303
336
|
|
|
@@ -357,10 +390,10 @@ export class DLOB {
|
|
|
357
390
|
return nodesToFill;
|
|
358
391
|
}
|
|
359
392
|
|
|
360
|
-
public
|
|
393
|
+
public findFallbackCrossingNodesToFill(
|
|
361
394
|
marketIndex: number,
|
|
362
|
-
|
|
363
|
-
|
|
395
|
+
fallbackBid: BN,
|
|
396
|
+
fallbackAsk: BN,
|
|
364
397
|
slot: number,
|
|
365
398
|
marketType: MarketType,
|
|
366
399
|
oraclePriceData: OraclePriceData
|
|
@@ -385,35 +418,67 @@ export class DLOB {
|
|
|
385
418
|
let nextAsk = askGenerator.next();
|
|
386
419
|
let nextBid = bidGenerator.next();
|
|
387
420
|
|
|
388
|
-
// check for asks that cross
|
|
389
|
-
while (!nextAsk.done) {
|
|
421
|
+
// check for asks that cross fallbackBid
|
|
422
|
+
while (!nextAsk.done && fallbackBid !== undefined) {
|
|
390
423
|
const askNode = nextAsk.value;
|
|
391
|
-
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
392
424
|
|
|
393
|
-
if (
|
|
425
|
+
if (isVariant(marketType, 'spot') && askNode.order?.postOnly) {
|
|
426
|
+
nextAsk = askGenerator.next();
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const askLimitPrice = getOptionalLimitPrice(
|
|
431
|
+
askNode.order,
|
|
432
|
+
oraclePriceData,
|
|
433
|
+
slot
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// order crosses if there is no limit price or it crosses fallback price
|
|
437
|
+
const crosses =
|
|
438
|
+
askLimitPrice === undefined || askLimitPrice.lte(fallbackBid);
|
|
439
|
+
|
|
440
|
+
// fallback is available if auction is complete or it's a spot order
|
|
441
|
+
const fallbackAvailable =
|
|
442
|
+
isVariant(marketType, 'spot') || isAuctionComplete(askNode.order, slot);
|
|
443
|
+
|
|
444
|
+
if (crosses && fallbackAvailable) {
|
|
394
445
|
nodesToFill.push({
|
|
395
446
|
node: askNode,
|
|
396
|
-
makerNode: undefined, // filled by
|
|
447
|
+
makerNode: undefined, // filled by fallback
|
|
397
448
|
});
|
|
398
|
-
} else {
|
|
399
|
-
break;
|
|
400
449
|
}
|
|
401
450
|
|
|
402
451
|
nextAsk = askGenerator.next();
|
|
403
452
|
}
|
|
404
453
|
|
|
405
|
-
// check for bids that cross
|
|
406
|
-
while (!nextBid.done) {
|
|
454
|
+
// check for bids that cross fallbackAsk
|
|
455
|
+
while (!nextBid.done && fallbackAsk !== undefined) {
|
|
407
456
|
const bidNode = nextBid.value;
|
|
408
|
-
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
409
457
|
|
|
410
|
-
if (
|
|
458
|
+
if (isVariant(marketType, 'spot') && bidNode.order?.postOnly) {
|
|
459
|
+
nextBid = bidGenerator.next();
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const bidLimitPrice = getOptionalLimitPrice(
|
|
464
|
+
bidNode.order,
|
|
465
|
+
oraclePriceData,
|
|
466
|
+
slot
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
// order crosses if there is no limit price or it crosses fallback price
|
|
470
|
+
const crosses =
|
|
471
|
+
bidLimitPrice === undefined || bidLimitPrice.gte(fallbackAsk);
|
|
472
|
+
|
|
473
|
+
// fallback is available if auction is complete or it's a spot order
|
|
474
|
+
const fallbackAvailable =
|
|
475
|
+
isVariant(marketType, 'spot') || isAuctionComplete(bidNode.order, slot);
|
|
476
|
+
|
|
477
|
+
if (crosses && fallbackAvailable) {
|
|
411
478
|
nodesToFill.push({
|
|
412
479
|
node: bidNode,
|
|
413
|
-
makerNode: undefined, // filled by
|
|
480
|
+
makerNode: undefined, // filled by fallback
|
|
414
481
|
});
|
|
415
|
-
} else {
|
|
416
|
-
break;
|
|
417
482
|
}
|
|
418
483
|
|
|
419
484
|
nextBid = bidGenerator.next();
|
|
@@ -422,28 +487,48 @@ export class DLOB {
|
|
|
422
487
|
return nodesToFill;
|
|
423
488
|
}
|
|
424
489
|
|
|
425
|
-
public
|
|
490
|
+
public findExpiredNodesToFill(
|
|
426
491
|
marketIndex: number,
|
|
427
|
-
|
|
492
|
+
ts: number,
|
|
428
493
|
marketType: MarketType
|
|
429
494
|
): NodeToFill[] {
|
|
430
495
|
const nodesToFill = new Array<NodeToFill>();
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
496
|
+
|
|
497
|
+
const marketTypeStr = getVariant(marketType) as MarketTypeStr;
|
|
498
|
+
const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
|
|
499
|
+
|
|
500
|
+
// All bids/asks that can expire
|
|
501
|
+
const bidGenerators = [
|
|
502
|
+
nodeLists.limit.bid.getGenerator(),
|
|
503
|
+
nodeLists.floatingLimit.bid.getGenerator(),
|
|
504
|
+
nodeLists.market.bid.getGenerator(),
|
|
505
|
+
];
|
|
506
|
+
const askGenerators = [
|
|
507
|
+
nodeLists.limit.ask.getGenerator(),
|
|
508
|
+
nodeLists.floatingLimit.ask.getGenerator(),
|
|
509
|
+
nodeLists.market.ask.getGenerator(),
|
|
510
|
+
];
|
|
511
|
+
|
|
512
|
+
for (const bidGenerator of bidGenerators) {
|
|
513
|
+
for (const bid of bidGenerator) {
|
|
514
|
+
if (isOrderExpired(bid.order, ts)) {
|
|
515
|
+
nodesToFill.push({
|
|
516
|
+
node: bid,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
437
519
|
}
|
|
438
520
|
}
|
|
439
521
|
|
|
440
|
-
for (const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
522
|
+
for (const askGenerator of askGenerators) {
|
|
523
|
+
for (const ask of askGenerator) {
|
|
524
|
+
if (isOrderExpired(ask.order, ts)) {
|
|
525
|
+
nodesToFill.push({
|
|
526
|
+
node: ask,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
445
529
|
}
|
|
446
530
|
}
|
|
531
|
+
|
|
447
532
|
return nodesToFill;
|
|
448
533
|
}
|
|
449
534
|
|
|
@@ -496,7 +581,7 @@ export class DLOB {
|
|
|
496
581
|
|
|
497
582
|
*getAsks(
|
|
498
583
|
marketIndex: number,
|
|
499
|
-
|
|
584
|
+
fallbackAsk: BN | undefined,
|
|
500
585
|
slot: number,
|
|
501
586
|
marketType: MarketType,
|
|
502
587
|
oraclePriceData: OraclePriceData
|
|
@@ -513,8 +598,11 @@ export class DLOB {
|
|
|
513
598
|
nodeLists.market.ask.getGenerator(),
|
|
514
599
|
];
|
|
515
600
|
|
|
516
|
-
if (marketTypeStr === 'perp' &&
|
|
517
|
-
generatorList.push(getVammNodeGenerator(
|
|
601
|
+
if (marketTypeStr === 'perp' && fallbackAsk) {
|
|
602
|
+
generatorList.push(getVammNodeGenerator(fallbackAsk));
|
|
603
|
+
}
|
|
604
|
+
if (generatorList.length === 0) {
|
|
605
|
+
throw new Error('No ask generators found');
|
|
518
606
|
}
|
|
519
607
|
|
|
520
608
|
const askGenerators = generatorList.map((generator) => {
|
|
@@ -558,6 +646,20 @@ export class DLOB {
|
|
|
558
646
|
continue;
|
|
559
647
|
}
|
|
560
648
|
|
|
649
|
+
// skip order if user is being liquidated/bankrupt
|
|
650
|
+
if (bestGenerator.next.value.userAccount !== undefined) {
|
|
651
|
+
const user = this.userMap.get(
|
|
652
|
+
bestGenerator.next.value.userAccount.toString()
|
|
653
|
+
);
|
|
654
|
+
if (
|
|
655
|
+
user?.getUserAccount().isBeingLiquidated ||
|
|
656
|
+
user?.getUserAccount().isBankrupt
|
|
657
|
+
) {
|
|
658
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
561
663
|
yield bestGenerator.next.value;
|
|
562
664
|
bestGenerator.next = bestGenerator.generator.next();
|
|
563
665
|
} else {
|
|
@@ -568,7 +670,7 @@ export class DLOB {
|
|
|
568
670
|
|
|
569
671
|
*getBids(
|
|
570
672
|
marketIndex: number,
|
|
571
|
-
|
|
673
|
+
fallbackBid: BN | undefined,
|
|
572
674
|
slot: number,
|
|
573
675
|
marketType: MarketType,
|
|
574
676
|
oraclePriceData: OraclePriceData
|
|
@@ -585,9 +687,13 @@ export class DLOB {
|
|
|
585
687
|
nodeLists.floatingLimit.bid.getGenerator(),
|
|
586
688
|
nodeLists.market.bid.getGenerator(),
|
|
587
689
|
];
|
|
588
|
-
if (marketTypeStr === 'perp' &&
|
|
589
|
-
generatorList.push(getVammNodeGenerator(
|
|
690
|
+
if (marketTypeStr === 'perp' && fallbackBid) {
|
|
691
|
+
generatorList.push(getVammNodeGenerator(fallbackBid));
|
|
590
692
|
}
|
|
693
|
+
if (generatorList.length === 0) {
|
|
694
|
+
throw new Error('No bid generators found');
|
|
695
|
+
}
|
|
696
|
+
|
|
591
697
|
const bidGenerators = generatorList.map((generator) => {
|
|
592
698
|
return {
|
|
593
699
|
next: generator.next(),
|
|
@@ -595,7 +701,7 @@ export class DLOB {
|
|
|
595
701
|
};
|
|
596
702
|
});
|
|
597
703
|
|
|
598
|
-
let bidsExhausted = false; // there will always be the
|
|
704
|
+
let bidsExhausted = false; // there will always be the fallbackBid
|
|
599
705
|
while (!bidsExhausted) {
|
|
600
706
|
const bestGenerator = bidGenerators.reduce(
|
|
601
707
|
(bestGenerator, currentGenerator) => {
|
|
@@ -629,6 +735,20 @@ export class DLOB {
|
|
|
629
735
|
continue;
|
|
630
736
|
}
|
|
631
737
|
|
|
738
|
+
// skip order if user is being liquidated/bankrupt
|
|
739
|
+
if (bestGenerator.next.value.userAccount !== undefined) {
|
|
740
|
+
const user = this.userMap.get(
|
|
741
|
+
bestGenerator.next.value.userAccount.toString()
|
|
742
|
+
);
|
|
743
|
+
if (
|
|
744
|
+
user?.getUserAccount().isBeingLiquidated ||
|
|
745
|
+
user?.getUserAccount().isBankrupt
|
|
746
|
+
) {
|
|
747
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
632
752
|
yield bestGenerator.next.value;
|
|
633
753
|
bestGenerator.next = bestGenerator.generator.next();
|
|
634
754
|
} else {
|
|
@@ -660,9 +780,16 @@ export class DLOB {
|
|
|
660
780
|
const bidOrder = bidNode.order;
|
|
661
781
|
const askOrder = askNode.order;
|
|
662
782
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
783
|
+
const bidUserAuthority = this.userMap.getUserAuthority(
|
|
784
|
+
bidNode.userAccount.toString()
|
|
785
|
+
);
|
|
786
|
+
const askUserAuthority = this.userMap.getUserAuthority(
|
|
787
|
+
askNode.userAccount.toString()
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
// Can't match orders from the same authority
|
|
791
|
+
const sameAuthority = bidUserAuthority.equals(askUserAuthority);
|
|
792
|
+
if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
|
|
666
793
|
// don't have a principle way to pick which one to exhaust,
|
|
667
794
|
// exhaust each one 50% of the time so we can try each one against other orders
|
|
668
795
|
const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
|
|
@@ -672,6 +799,19 @@ export class DLOB {
|
|
|
672
799
|
};
|
|
673
800
|
}
|
|
674
801
|
|
|
802
|
+
const { takerNode, makerNode, makerSide } = this.determineMakerAndTaker(
|
|
803
|
+
askNode,
|
|
804
|
+
bidNode
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
// If order doesn't have limit price, cant be maker
|
|
808
|
+
if (!hasLimitPrice(makerNode.order, slot)) {
|
|
809
|
+
return {
|
|
810
|
+
crossingNodes: [],
|
|
811
|
+
exhaustedSide: makerSide,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
675
815
|
const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
|
|
676
816
|
bidOrder.baseAssetAmountFilled
|
|
677
817
|
);
|
|
@@ -744,68 +884,92 @@ export class DLOB {
|
|
|
744
884
|
);
|
|
745
885
|
}
|
|
746
886
|
|
|
747
|
-
// Bid is maker
|
|
748
|
-
if (bidOrder.postOnly) {
|
|
749
|
-
return {
|
|
750
|
-
crossingNodes: [
|
|
751
|
-
{
|
|
752
|
-
node: askNode,
|
|
753
|
-
makerNode: bidNode,
|
|
754
|
-
},
|
|
755
|
-
],
|
|
756
|
-
exhaustedSide,
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// Ask is maker
|
|
761
|
-
if (askOrder.postOnly) {
|
|
762
|
-
return {
|
|
763
|
-
crossingNodes: [
|
|
764
|
-
{
|
|
765
|
-
node: bidNode,
|
|
766
|
-
makerNode: askNode,
|
|
767
|
-
},
|
|
768
|
-
],
|
|
769
|
-
exhaustedSide,
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// Both are takers
|
|
774
|
-
// older order is maker
|
|
775
|
-
const [olderNode, newerNode] = askOrder.ts.lt(bidOrder.ts)
|
|
776
|
-
? [askNode, bidNode]
|
|
777
|
-
: [bidNode, askNode];
|
|
778
887
|
return {
|
|
779
888
|
crossingNodes: [
|
|
780
889
|
{
|
|
781
|
-
node:
|
|
782
|
-
makerNode:
|
|
890
|
+
node: takerNode,
|
|
891
|
+
makerNode: makerNode,
|
|
783
892
|
},
|
|
784
893
|
],
|
|
785
894
|
exhaustedSide,
|
|
786
895
|
};
|
|
787
896
|
}
|
|
788
897
|
|
|
898
|
+
determineMakerAndTaker(
|
|
899
|
+
askNode: DLOBNode,
|
|
900
|
+
bidNode: DLOBNode
|
|
901
|
+
): { takerNode: DLOBNode; makerNode: DLOBNode; makerSide: Side } {
|
|
902
|
+
if (bidNode.order.postOnly) {
|
|
903
|
+
return {
|
|
904
|
+
takerNode: askNode,
|
|
905
|
+
makerNode: bidNode,
|
|
906
|
+
makerSide: 'bid',
|
|
907
|
+
};
|
|
908
|
+
} else if (askNode.order.postOnly) {
|
|
909
|
+
return {
|
|
910
|
+
takerNode: bidNode,
|
|
911
|
+
makerNode: askNode,
|
|
912
|
+
makerSide: 'ask',
|
|
913
|
+
};
|
|
914
|
+
} else if (isMarketOrder(bidNode.order) && isLimitOrder(askNode.order)) {
|
|
915
|
+
return {
|
|
916
|
+
takerNode: bidNode,
|
|
917
|
+
makerNode: askNode,
|
|
918
|
+
makerSide: 'ask',
|
|
919
|
+
};
|
|
920
|
+
} else if (isMarketOrder(askNode.order) && isLimitOrder(bidNode.order)) {
|
|
921
|
+
return {
|
|
922
|
+
takerNode: askNode,
|
|
923
|
+
makerNode: bidNode,
|
|
924
|
+
makerSide: 'bid',
|
|
925
|
+
};
|
|
926
|
+
} else if (askNode.order.slot.lt(bidNode.order.slot)) {
|
|
927
|
+
return {
|
|
928
|
+
takerNode: bidNode,
|
|
929
|
+
makerNode: askNode,
|
|
930
|
+
makerSide: 'ask',
|
|
931
|
+
};
|
|
932
|
+
} else {
|
|
933
|
+
return {
|
|
934
|
+
takerNode: askNode,
|
|
935
|
+
makerNode: bidNode,
|
|
936
|
+
makerSide: 'bid',
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
789
941
|
public getBestAsk(
|
|
790
942
|
marketIndex: number,
|
|
791
|
-
|
|
943
|
+
fallbackAsk: BN | undefined,
|
|
792
944
|
slot: number,
|
|
793
945
|
marketType: MarketType,
|
|
794
946
|
oraclePriceData: OraclePriceData
|
|
795
947
|
): BN {
|
|
796
|
-
return this.getAsks(
|
|
948
|
+
return this.getAsks(
|
|
949
|
+
marketIndex,
|
|
950
|
+
fallbackAsk,
|
|
951
|
+
slot,
|
|
952
|
+
marketType,
|
|
953
|
+
oraclePriceData
|
|
954
|
+
)
|
|
797
955
|
.next()
|
|
798
956
|
.value.getPrice(oraclePriceData, slot);
|
|
799
957
|
}
|
|
800
958
|
|
|
801
959
|
public getBestBid(
|
|
802
960
|
marketIndex: number,
|
|
803
|
-
|
|
961
|
+
fallbackBid: BN | undefined,
|
|
804
962
|
slot: number,
|
|
805
963
|
marketType: MarketType,
|
|
806
964
|
oraclePriceData: OraclePriceData
|
|
807
965
|
): BN {
|
|
808
|
-
return this.getBids(
|
|
966
|
+
return this.getBids(
|
|
967
|
+
marketIndex,
|
|
968
|
+
fallbackBid,
|
|
969
|
+
slot,
|
|
970
|
+
marketType,
|
|
971
|
+
oraclePriceData
|
|
972
|
+
)
|
|
809
973
|
.next()
|
|
810
974
|
.value.getPrice(oraclePriceData, slot);
|
|
811
975
|
}
|
|
@@ -816,6 +980,10 @@ export class DLOB {
|
|
|
816
980
|
oraclePrice: BN,
|
|
817
981
|
marketType: MarketType
|
|
818
982
|
): NodeToTrigger[] {
|
|
983
|
+
if (exchangePaused(this.stateAccount)) {
|
|
984
|
+
return [];
|
|
985
|
+
}
|
|
986
|
+
|
|
819
987
|
const nodesToTrigger = [];
|
|
820
988
|
const marketTypeStr = getVariant(marketType) as MarketTypeStr;
|
|
821
989
|
for (const node of this.orderLists
|
|
@@ -862,20 +1030,21 @@ export class DLOB {
|
|
|
862
1030
|
const market = clearingHouse.getPerpMarketAccount(marketIndex);
|
|
863
1031
|
|
|
864
1032
|
const slot = slotSubscriber.getSlot();
|
|
865
|
-
const oraclePriceData =
|
|
866
|
-
|
|
867
|
-
const
|
|
1033
|
+
const oraclePriceData =
|
|
1034
|
+
clearingHouse.getOracleDataForPerpMarket(marketIndex);
|
|
1035
|
+
const fallbackAsk = calculateAskPrice(market, oraclePriceData);
|
|
1036
|
+
const fallbackBid = calculateBidPrice(market, oraclePriceData);
|
|
868
1037
|
|
|
869
1038
|
const bestAsk = this.getBestAsk(
|
|
870
1039
|
marketIndex,
|
|
871
|
-
|
|
1040
|
+
fallbackAsk,
|
|
872
1041
|
slot,
|
|
873
1042
|
marketType,
|
|
874
1043
|
oraclePriceData
|
|
875
1044
|
);
|
|
876
1045
|
const bestBid = this.getBestBid(
|
|
877
1046
|
marketIndex,
|
|
878
|
-
|
|
1047
|
+
fallbackBid,
|
|
879
1048
|
slot,
|
|
880
1049
|
marketType,
|
|
881
1050
|
oraclePriceData
|
|
@@ -907,7 +1076,8 @@ export class DLOB {
|
|
|
907
1076
|
);
|
|
908
1077
|
} else if (isVariant(marketType, 'spot')) {
|
|
909
1078
|
const slot = slotSubscriber.getSlot();
|
|
910
|
-
const oraclePriceData =
|
|
1079
|
+
const oraclePriceData =
|
|
1080
|
+
clearingHouse.getOracleDataForPerpMarket(marketIndex);
|
|
911
1081
|
|
|
912
1082
|
const bestAsk = this.getBestAsk(
|
|
913
1083
|
marketIndex,
|
package/src/dlob/DLOBNode.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
isOneOfVariant,
|
|
3
2
|
AMM_RESERVE_PRECISION,
|
|
4
3
|
BN,
|
|
5
4
|
convertToNumber,
|
|
@@ -74,18 +73,7 @@ export abstract class OrderNode implements DLOBNode {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
getPrice(oraclePriceData: OraclePriceData, slot: number): BN {
|
|
77
|
-
|
|
78
|
-
return getLimitPrice(this.order, oraclePriceData, slot);
|
|
79
|
-
} else if (isOneOfVariant(this.order.marketType, ['perp'])) {
|
|
80
|
-
return getLimitPrice(
|
|
81
|
-
this.order,
|
|
82
|
-
oraclePriceData,
|
|
83
|
-
slot,
|
|
84
|
-
this.market as PerpMarketAccount
|
|
85
|
-
);
|
|
86
|
-
} else {
|
|
87
|
-
console.error(`Unknown market type: ${this.order.marketType}`);
|
|
88
|
-
}
|
|
76
|
+
return getLimitPrice(this.order, oraclePriceData, slot);
|
|
89
77
|
}
|
|
90
78
|
|
|
91
79
|
isBaseFilled(): boolean {
|
|
@@ -111,7 +99,7 @@ export class FloatingLimitOrderNode extends OrderNode {
|
|
|
111
99
|
previous?: FloatingLimitOrderNode;
|
|
112
100
|
|
|
113
101
|
getSortValue(order: Order): BN {
|
|
114
|
-
return order.oraclePriceOffset;
|
|
102
|
+
return new BN(order.oraclePriceOffset);
|
|
115
103
|
}
|
|
116
104
|
}
|
|
117
105
|
|