@drift-labs/sdk 0.2.0-master.32 → 0.2.0-master.34
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 +18 -11
- package/lib/dlob/DLOB.js +179 -107
- package/lib/dlob/DLOBNode.js +2 -10
- package/lib/dlob/NodeList.js +1 -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 +1540 -1246
- 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 +60 -75
- package/lib/types.js +2 -1
- 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/assert/assert.js +9 -0
- 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 +257 -120
- package/src/dlob/DLOBNode.ts +2 -14
- package/src/dlob/NodeList.ts +1 -1
- package/src/events/eventList.js +77 -0
- 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/examples/makeTradeExample.js +157 -0
- package/src/factory/bigNum.ts +59 -6
- package/src/idl/clearing_house.json +1540 -1246
- 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/token/index.js +38 -0
- package/src/tx/types.js +2 -0
- package/src/tx/utils.js +17 -0
- package/src/types.ts +65 -51
- package/src/userMap/userMap.ts +25 -1
- package/src/userName.ts +2 -1
- package/src/util/computeUnits.js +27 -0
- package/src/util/getTokenAddress.js +9 -0
- package/src/util/promiseTimeout.js +14 -0
- package/src/util/tps.js +27 -0
- package/tests/bn/test.ts +22 -1
- package/tests/dlob/helpers.ts +252 -81
- package/tests/dlob/test.ts +1040 -219
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>());
|
|
@@ -164,36 +176,20 @@ export class DLOB {
|
|
|
164
176
|
/**
|
|
165
177
|
* initializes a new DLOB instance
|
|
166
178
|
*
|
|
167
|
-
* @param clearingHouse The ClearingHouse instance to use for price data
|
|
168
179
|
* @returns a promise that resolves when the DLOB is initialized
|
|
169
180
|
*/
|
|
170
|
-
public async init(
|
|
171
|
-
clearingHouse: ClearingHouse,
|
|
172
|
-
userMap?: UserMap
|
|
173
|
-
): Promise<boolean> {
|
|
181
|
+
public async init(): Promise<boolean> {
|
|
174
182
|
if (this.initialized) {
|
|
175
183
|
return false;
|
|
176
184
|
}
|
|
177
|
-
if (userMap) {
|
|
178
|
-
// initialize the dlob with the user map (prevents hitting getProgramAccounts)
|
|
179
|
-
for (const user of userMap.values()) {
|
|
180
|
-
const userAccount = user.getUserAccount();
|
|
181
|
-
const userAccountPubkey = user.getUserAccountPublicKey();
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
// @ts-ignore
|
|
191
|
-
const userAccount: UserAccount = programAccount.account;
|
|
192
|
-
const userAccountPublicKey = programAccount.publicKey;
|
|
193
|
-
|
|
194
|
-
for (const order of userAccount.orders) {
|
|
195
|
-
this.insertOrder(order, userAccountPublicKey);
|
|
196
|
-
}
|
|
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);
|
|
197
193
|
}
|
|
198
194
|
}
|
|
199
195
|
|
|
@@ -265,7 +261,7 @@ export class DLOB {
|
|
|
265
261
|
type = 'trigger';
|
|
266
262
|
} else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
|
|
267
263
|
type = 'market';
|
|
268
|
-
} else if (
|
|
264
|
+
} else if (order.oraclePriceOffset !== 0) {
|
|
269
265
|
type = 'floatingLimit';
|
|
270
266
|
} else {
|
|
271
267
|
type = 'limit';
|
|
@@ -291,12 +287,21 @@ export class DLOB {
|
|
|
291
287
|
|
|
292
288
|
public findNodesToFill(
|
|
293
289
|
marketIndex: number,
|
|
294
|
-
|
|
295
|
-
|
|
290
|
+
fallbackBid: BN | undefined,
|
|
291
|
+
fallbackAsk: BN | undefined,
|
|
296
292
|
slot: number,
|
|
293
|
+
ts: number,
|
|
297
294
|
marketType: MarketType,
|
|
298
295
|
oraclePriceData: OraclePriceData
|
|
299
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
|
+
|
|
300
305
|
// Find all the crossing nodes
|
|
301
306
|
const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
|
|
302
307
|
marketIndex,
|
|
@@ -305,25 +310,27 @@ export class DLOB {
|
|
|
305
310
|
oraclePriceData
|
|
306
311
|
);
|
|
307
312
|
|
|
308
|
-
|
|
309
|
-
|
|
313
|
+
let fallbackCrossingNodesToFill = new Array<NodeToFill>();
|
|
314
|
+
if (!ammPaused(this.stateAccount, marketAccount)) {
|
|
315
|
+
fallbackCrossingNodesToFill = this.findFallbackCrossingNodesToFill(
|
|
310
316
|
marketIndex,
|
|
311
|
-
|
|
312
|
-
|
|
317
|
+
fallbackBid,
|
|
318
|
+
fallbackAsk,
|
|
313
319
|
slot,
|
|
314
320
|
marketType,
|
|
315
321
|
oraclePriceData
|
|
316
322
|
);
|
|
323
|
+
}
|
|
317
324
|
|
|
318
325
|
// get expired market nodes
|
|
319
|
-
const
|
|
326
|
+
const expiredNodesToFill = this.findExpiredNodesToFill(
|
|
320
327
|
marketIndex,
|
|
321
|
-
|
|
328
|
+
ts,
|
|
322
329
|
marketType
|
|
323
330
|
);
|
|
324
331
|
return crossingNodesToFill.concat(
|
|
325
|
-
|
|
326
|
-
|
|
332
|
+
fallbackCrossingNodesToFill,
|
|
333
|
+
expiredNodesToFill
|
|
327
334
|
);
|
|
328
335
|
}
|
|
329
336
|
|
|
@@ -383,10 +390,10 @@ export class DLOB {
|
|
|
383
390
|
return nodesToFill;
|
|
384
391
|
}
|
|
385
392
|
|
|
386
|
-
public
|
|
393
|
+
public findFallbackCrossingNodesToFill(
|
|
387
394
|
marketIndex: number,
|
|
388
|
-
|
|
389
|
-
|
|
395
|
+
fallbackBid: BN,
|
|
396
|
+
fallbackAsk: BN,
|
|
390
397
|
slot: number,
|
|
391
398
|
marketType: MarketType,
|
|
392
399
|
oraclePriceData: OraclePriceData
|
|
@@ -411,35 +418,67 @@ export class DLOB {
|
|
|
411
418
|
let nextAsk = askGenerator.next();
|
|
412
419
|
let nextBid = bidGenerator.next();
|
|
413
420
|
|
|
414
|
-
// check for asks that cross
|
|
415
|
-
while (!nextAsk.done) {
|
|
421
|
+
// check for asks that cross fallbackBid
|
|
422
|
+
while (!nextAsk.done && fallbackBid !== undefined) {
|
|
416
423
|
const askNode = nextAsk.value;
|
|
417
|
-
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
418
424
|
|
|
419
|
-
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) {
|
|
420
445
|
nodesToFill.push({
|
|
421
446
|
node: askNode,
|
|
422
|
-
makerNode: undefined, // filled by
|
|
447
|
+
makerNode: undefined, // filled by fallback
|
|
423
448
|
});
|
|
424
|
-
} else {
|
|
425
|
-
break;
|
|
426
449
|
}
|
|
427
450
|
|
|
428
451
|
nextAsk = askGenerator.next();
|
|
429
452
|
}
|
|
430
453
|
|
|
431
|
-
// check for bids that cross
|
|
432
|
-
while (!nextBid.done) {
|
|
454
|
+
// check for bids that cross fallbackAsk
|
|
455
|
+
while (!nextBid.done && fallbackAsk !== undefined) {
|
|
433
456
|
const bidNode = nextBid.value;
|
|
434
|
-
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
435
457
|
|
|
436
|
-
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) {
|
|
437
478
|
nodesToFill.push({
|
|
438
479
|
node: bidNode,
|
|
439
|
-
makerNode: undefined, // filled by
|
|
480
|
+
makerNode: undefined, // filled by fallback
|
|
440
481
|
});
|
|
441
|
-
} else {
|
|
442
|
-
break;
|
|
443
482
|
}
|
|
444
483
|
|
|
445
484
|
nextBid = bidGenerator.next();
|
|
@@ -448,28 +487,48 @@ export class DLOB {
|
|
|
448
487
|
return nodesToFill;
|
|
449
488
|
}
|
|
450
489
|
|
|
451
|
-
public
|
|
490
|
+
public findExpiredNodesToFill(
|
|
452
491
|
marketIndex: number,
|
|
453
|
-
|
|
492
|
+
ts: number,
|
|
454
493
|
marketType: MarketType
|
|
455
494
|
): NodeToFill[] {
|
|
456
495
|
const nodesToFill = new Array<NodeToFill>();
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
+
}
|
|
463
519
|
}
|
|
464
520
|
}
|
|
465
521
|
|
|
466
|
-
for (const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
}
|
|
471
529
|
}
|
|
472
530
|
}
|
|
531
|
+
|
|
473
532
|
return nodesToFill;
|
|
474
533
|
}
|
|
475
534
|
|
|
@@ -522,7 +581,7 @@ export class DLOB {
|
|
|
522
581
|
|
|
523
582
|
*getAsks(
|
|
524
583
|
marketIndex: number,
|
|
525
|
-
|
|
584
|
+
fallbackAsk: BN | undefined,
|
|
526
585
|
slot: number,
|
|
527
586
|
marketType: MarketType,
|
|
528
587
|
oraclePriceData: OraclePriceData
|
|
@@ -539,8 +598,8 @@ export class DLOB {
|
|
|
539
598
|
nodeLists.market.ask.getGenerator(),
|
|
540
599
|
];
|
|
541
600
|
|
|
542
|
-
if (marketTypeStr === 'perp' &&
|
|
543
|
-
generatorList.push(getVammNodeGenerator(
|
|
601
|
+
if (marketTypeStr === 'perp' && fallbackAsk) {
|
|
602
|
+
generatorList.push(getVammNodeGenerator(fallbackAsk));
|
|
544
603
|
}
|
|
545
604
|
if (generatorList.length === 0) {
|
|
546
605
|
throw new Error('No ask generators found');
|
|
@@ -587,6 +646,20 @@ export class DLOB {
|
|
|
587
646
|
continue;
|
|
588
647
|
}
|
|
589
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
|
+
|
|
590
663
|
yield bestGenerator.next.value;
|
|
591
664
|
bestGenerator.next = bestGenerator.generator.next();
|
|
592
665
|
} else {
|
|
@@ -597,7 +670,7 @@ export class DLOB {
|
|
|
597
670
|
|
|
598
671
|
*getBids(
|
|
599
672
|
marketIndex: number,
|
|
600
|
-
|
|
673
|
+
fallbackBid: BN | undefined,
|
|
601
674
|
slot: number,
|
|
602
675
|
marketType: MarketType,
|
|
603
676
|
oraclePriceData: OraclePriceData
|
|
@@ -614,8 +687,8 @@ export class DLOB {
|
|
|
614
687
|
nodeLists.floatingLimit.bid.getGenerator(),
|
|
615
688
|
nodeLists.market.bid.getGenerator(),
|
|
616
689
|
];
|
|
617
|
-
if (marketTypeStr === 'perp' &&
|
|
618
|
-
generatorList.push(getVammNodeGenerator(
|
|
690
|
+
if (marketTypeStr === 'perp' && fallbackBid) {
|
|
691
|
+
generatorList.push(getVammNodeGenerator(fallbackBid));
|
|
619
692
|
}
|
|
620
693
|
if (generatorList.length === 0) {
|
|
621
694
|
throw new Error('No bid generators found');
|
|
@@ -628,7 +701,7 @@ export class DLOB {
|
|
|
628
701
|
};
|
|
629
702
|
});
|
|
630
703
|
|
|
631
|
-
let bidsExhausted = false; // there will always be the
|
|
704
|
+
let bidsExhausted = false; // there will always be the fallbackBid
|
|
632
705
|
while (!bidsExhausted) {
|
|
633
706
|
const bestGenerator = bidGenerators.reduce(
|
|
634
707
|
(bestGenerator, currentGenerator) => {
|
|
@@ -662,6 +735,20 @@ export class DLOB {
|
|
|
662
735
|
continue;
|
|
663
736
|
}
|
|
664
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
|
+
|
|
665
752
|
yield bestGenerator.next.value;
|
|
666
753
|
bestGenerator.next = bestGenerator.generator.next();
|
|
667
754
|
} else {
|
|
@@ -693,9 +780,16 @@ export class DLOB {
|
|
|
693
780
|
const bidOrder = bidNode.order;
|
|
694
781
|
const askOrder = askNode.order;
|
|
695
782
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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)) {
|
|
699
793
|
// don't have a principle way to pick which one to exhaust,
|
|
700
794
|
// exhaust each one 50% of the time so we can try each one against other orders
|
|
701
795
|
const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
|
|
@@ -705,6 +799,19 @@ export class DLOB {
|
|
|
705
799
|
};
|
|
706
800
|
}
|
|
707
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
|
+
|
|
708
815
|
const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
|
|
709
816
|
bidOrder.baseAssetAmountFilled
|
|
710
817
|
);
|
|
@@ -777,68 +884,92 @@ export class DLOB {
|
|
|
777
884
|
);
|
|
778
885
|
}
|
|
779
886
|
|
|
780
|
-
// Bid is maker
|
|
781
|
-
if (bidOrder.postOnly) {
|
|
782
|
-
return {
|
|
783
|
-
crossingNodes: [
|
|
784
|
-
{
|
|
785
|
-
node: askNode,
|
|
786
|
-
makerNode: bidNode,
|
|
787
|
-
},
|
|
788
|
-
],
|
|
789
|
-
exhaustedSide,
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Ask is maker
|
|
794
|
-
if (askOrder.postOnly) {
|
|
795
|
-
return {
|
|
796
|
-
crossingNodes: [
|
|
797
|
-
{
|
|
798
|
-
node: bidNode,
|
|
799
|
-
makerNode: askNode,
|
|
800
|
-
},
|
|
801
|
-
],
|
|
802
|
-
exhaustedSide,
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// Both are takers
|
|
807
|
-
// older order is maker
|
|
808
|
-
const [olderNode, newerNode] = askOrder.ts.lt(bidOrder.ts)
|
|
809
|
-
? [askNode, bidNode]
|
|
810
|
-
: [bidNode, askNode];
|
|
811
887
|
return {
|
|
812
888
|
crossingNodes: [
|
|
813
889
|
{
|
|
814
|
-
node:
|
|
815
|
-
makerNode:
|
|
890
|
+
node: takerNode,
|
|
891
|
+
makerNode: makerNode,
|
|
816
892
|
},
|
|
817
893
|
],
|
|
818
894
|
exhaustedSide,
|
|
819
895
|
};
|
|
820
896
|
}
|
|
821
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
|
+
|
|
822
941
|
public getBestAsk(
|
|
823
942
|
marketIndex: number,
|
|
824
|
-
|
|
943
|
+
fallbackAsk: BN | undefined,
|
|
825
944
|
slot: number,
|
|
826
945
|
marketType: MarketType,
|
|
827
946
|
oraclePriceData: OraclePriceData
|
|
828
947
|
): BN {
|
|
829
|
-
return this.getAsks(
|
|
948
|
+
return this.getAsks(
|
|
949
|
+
marketIndex,
|
|
950
|
+
fallbackAsk,
|
|
951
|
+
slot,
|
|
952
|
+
marketType,
|
|
953
|
+
oraclePriceData
|
|
954
|
+
)
|
|
830
955
|
.next()
|
|
831
956
|
.value.getPrice(oraclePriceData, slot);
|
|
832
957
|
}
|
|
833
958
|
|
|
834
959
|
public getBestBid(
|
|
835
960
|
marketIndex: number,
|
|
836
|
-
|
|
961
|
+
fallbackBid: BN | undefined,
|
|
837
962
|
slot: number,
|
|
838
963
|
marketType: MarketType,
|
|
839
964
|
oraclePriceData: OraclePriceData
|
|
840
965
|
): BN {
|
|
841
|
-
return this.getBids(
|
|
966
|
+
return this.getBids(
|
|
967
|
+
marketIndex,
|
|
968
|
+
fallbackBid,
|
|
969
|
+
slot,
|
|
970
|
+
marketType,
|
|
971
|
+
oraclePriceData
|
|
972
|
+
)
|
|
842
973
|
.next()
|
|
843
974
|
.value.getPrice(oraclePriceData, slot);
|
|
844
975
|
}
|
|
@@ -849,6 +980,10 @@ export class DLOB {
|
|
|
849
980
|
oraclePrice: BN,
|
|
850
981
|
marketType: MarketType
|
|
851
982
|
): NodeToTrigger[] {
|
|
983
|
+
if (exchangePaused(this.stateAccount)) {
|
|
984
|
+
return [];
|
|
985
|
+
}
|
|
986
|
+
|
|
852
987
|
const nodesToTrigger = [];
|
|
853
988
|
const marketTypeStr = getVariant(marketType) as MarketTypeStr;
|
|
854
989
|
for (const node of this.orderLists
|
|
@@ -895,20 +1030,21 @@ export class DLOB {
|
|
|
895
1030
|
const market = clearingHouse.getPerpMarketAccount(marketIndex);
|
|
896
1031
|
|
|
897
1032
|
const slot = slotSubscriber.getSlot();
|
|
898
|
-
const oraclePriceData =
|
|
899
|
-
|
|
900
|
-
const
|
|
1033
|
+
const oraclePriceData =
|
|
1034
|
+
clearingHouse.getOracleDataForPerpMarket(marketIndex);
|
|
1035
|
+
const fallbackAsk = calculateAskPrice(market, oraclePriceData);
|
|
1036
|
+
const fallbackBid = calculateBidPrice(market, oraclePriceData);
|
|
901
1037
|
|
|
902
1038
|
const bestAsk = this.getBestAsk(
|
|
903
1039
|
marketIndex,
|
|
904
|
-
|
|
1040
|
+
fallbackAsk,
|
|
905
1041
|
slot,
|
|
906
1042
|
marketType,
|
|
907
1043
|
oraclePriceData
|
|
908
1044
|
);
|
|
909
1045
|
const bestBid = this.getBestBid(
|
|
910
1046
|
marketIndex,
|
|
911
|
-
|
|
1047
|
+
fallbackBid,
|
|
912
1048
|
slot,
|
|
913
1049
|
marketType,
|
|
914
1050
|
oraclePriceData
|
|
@@ -940,7 +1076,8 @@ export class DLOB {
|
|
|
940
1076
|
);
|
|
941
1077
|
} else if (isVariant(marketType, 'spot')) {
|
|
942
1078
|
const slot = slotSubscriber.getSlot();
|
|
943
|
-
const oraclePriceData =
|
|
1079
|
+
const oraclePriceData =
|
|
1080
|
+
clearingHouse.getOracleDataForPerpMarket(marketIndex);
|
|
944
1081
|
|
|
945
1082
|
const bestAsk = this.getBestAsk(
|
|
946
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
|
|
package/src/dlob/NodeList.ts
CHANGED
|
@@ -105,7 +105,7 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
|
|
|
105
105
|
const newOrderSortPrice = newNode.sortValue;
|
|
106
106
|
|
|
107
107
|
if (newOrderSortPrice.eq(currentOrderSortPrice)) {
|
|
108
|
-
return newOrder.
|
|
108
|
+
return newOrder.slot.lt(currentOrder.slot);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (this.sortDirection === 'asc') {
|