@drift-labs/sdk 0.2.0-master.30 → 0.2.0-master.32
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/admin.d.ts +11 -7
- package/lib/admin.js +50 -12
- package/lib/clearingHouse.d.ts +26 -23
- package/lib/clearingHouse.js +290 -652
- package/lib/clearingHouseUser.d.ts +1 -1
- package/lib/clearingHouseUser.js +9 -12
- package/lib/config.js +1 -1
- package/lib/constants/numericConstants.d.ts +7 -0
- package/lib/constants/numericConstants.js +8 -1
- package/lib/constants/spotMarkets.js +4 -4
- package/lib/dlob/DLOB.d.ts +7 -5
- package/lib/dlob/DLOB.js +159 -88
- package/lib/dlob/DLOBNode.d.ts +2 -0
- package/lib/dlob/DLOBNode.js +3 -0
- package/lib/dlob/NodeList.d.ts +2 -1
- package/lib/dlob/NodeList.js +10 -4
- package/lib/events/types.d.ts +2 -1
- package/lib/events/types.js +1 -0
- package/lib/factory/bigNum.d.ts +1 -0
- package/lib/factory/bigNum.js +14 -0
- package/lib/idl/clearing_house.json +1091 -611
- package/lib/math/amm.d.ts +2 -2
- package/lib/math/amm.js +31 -28
- package/lib/math/market.d.ts +1 -1
- package/lib/math/market.js +6 -6
- package/lib/math/spotBalance.js +7 -8
- package/lib/math/trade.js +11 -11
- package/lib/types.d.ts +118 -41
- package/lib/types.js +34 -4
- package/package.json +4 -2
- package/src/admin.ts +129 -29
- package/src/clearingHouse.ts +380 -787
- package/src/clearingHouseUser.ts +11 -14
- package/src/config.ts +1 -1
- package/src/constants/numericConstants.ts +7 -0
- package/src/constants/spotMarkets.ts +5 -4
- package/src/dlob/DLOB.ts +221 -103
- package/src/dlob/DLOBNode.ts +5 -0
- package/src/dlob/NodeList.ts +15 -5
- package/src/events/types.ts +3 -0
- package/src/factory/bigNum.ts +23 -0
- package/src/idl/clearing_house.json +1091 -611
- package/src/math/amm.ts +42 -29
- package/src/math/market.ts +9 -4
- package/src/math/spotBalance.ts +11 -8
- package/src/math/trade.ts +11 -11
- package/src/types.ts +82 -41
- package/tests/bn/test.ts +13 -8
- package/tests/dlob/helpers.ts +56 -33
- package/tests/dlob/test.ts +1397 -495
- package/src/assert/assert.js +0 -9
- package/src/events/eventList.js +0 -77
- package/src/examples/makeTradeExample.js +0 -157
- package/src/token/index.js +0 -38
- package/src/tx/types.js +0 -2
- package/src/tx/utils.js +0 -17
- package/src/util/computeUnits.js +0 -27
- package/src/util/getTokenAddress.js +0 -9
- package/src/util/promiseTimeout.js +0 -14
- package/src/util/tps.js +0 -27
package/src/clearingHouseUser.ts
CHANGED
|
@@ -134,7 +134,7 @@ export class ClearingHouseUser {
|
|
|
134
134
|
public getEmptyPosition(marketIndex: number): PerpPosition {
|
|
135
135
|
return {
|
|
136
136
|
baseAssetAmount: ZERO,
|
|
137
|
-
remainderBaseAssetAmount:
|
|
137
|
+
remainderBaseAssetAmount: 0,
|
|
138
138
|
lastCumulativeFundingRate: ZERO,
|
|
139
139
|
marketIndex,
|
|
140
140
|
quoteAssetAmount: ZERO,
|
|
@@ -144,7 +144,6 @@ export class ClearingHouseUser {
|
|
|
144
144
|
openAsks: ZERO,
|
|
145
145
|
settledPnl: ZERO,
|
|
146
146
|
lpShares: ZERO,
|
|
147
|
-
lastFeePerLp: ZERO,
|
|
148
147
|
lastNetBaseAssetAmountPerLp: ZERO,
|
|
149
148
|
lastNetQuoteAssetAmountPerLp: ZERO,
|
|
150
149
|
};
|
|
@@ -159,9 +158,9 @@ export class ClearingHouseUser {
|
|
|
159
158
|
* @param orderId
|
|
160
159
|
* @returns Order
|
|
161
160
|
*/
|
|
162
|
-
public getOrder(orderId:
|
|
163
|
-
return this.getUserAccount().orders.find(
|
|
164
|
-
order.orderId
|
|
161
|
+
public getOrder(orderId: number): Order | undefined {
|
|
162
|
+
return this.getUserAccount().orders.find(
|
|
163
|
+
(order) => order.orderId === orderId
|
|
165
164
|
);
|
|
166
165
|
}
|
|
167
166
|
|
|
@@ -229,13 +228,11 @@ export class ClearingHouseUser {
|
|
|
229
228
|
market.amm.baseAssetAmountStepSize
|
|
230
229
|
);
|
|
231
230
|
|
|
232
|
-
position.remainderBaseAssetAmount
|
|
233
|
-
position.remainderBaseAssetAmount.add(remainderBaa);
|
|
231
|
+
position.remainderBaseAssetAmount += remainderBaa.toNumber();
|
|
234
232
|
|
|
235
233
|
if (
|
|
236
|
-
position.remainderBaseAssetAmount
|
|
237
|
-
|
|
238
|
-
.gte(market.amm.baseAssetAmountStepSize)
|
|
234
|
+
Math.abs(position.remainderBaseAssetAmount) >
|
|
235
|
+
market.amm.baseAssetAmountStepSize.toNumber()
|
|
239
236
|
) {
|
|
240
237
|
const [newStandardizedBaa, newRemainderBaa] = standardize(
|
|
241
238
|
position.remainderBaseAssetAmount,
|
|
@@ -243,7 +240,7 @@ export class ClearingHouseUser {
|
|
|
243
240
|
);
|
|
244
241
|
position.baseAssetAmount =
|
|
245
242
|
position.baseAssetAmount.add(newStandardizedBaa);
|
|
246
|
-
position.remainderBaseAssetAmount = newRemainderBaa;
|
|
243
|
+
position.remainderBaseAssetAmount = newRemainderBaa.toNumber();
|
|
247
244
|
}
|
|
248
245
|
|
|
249
246
|
let updateType;
|
|
@@ -492,7 +489,7 @@ export class ClearingHouseUser {
|
|
|
492
489
|
let newTotalLiabilityValue = totalLiabilityValue;
|
|
493
490
|
if (worstCaseTokenAmount.lt(ZERO)) {
|
|
494
491
|
const baseLiabilityValue = this.getSpotLiabilityValue(
|
|
495
|
-
worstCaseTokenAmount,
|
|
492
|
+
worstCaseTokenAmount.abs(),
|
|
496
493
|
oraclePriceData,
|
|
497
494
|
spotMarketAccount,
|
|
498
495
|
marginCategory,
|
|
@@ -513,6 +510,7 @@ export class ClearingHouseUser {
|
|
|
513
510
|
}
|
|
514
511
|
|
|
515
512
|
const weightedTokenValue = worstCaseQuoteTokenAmount
|
|
513
|
+
.abs()
|
|
516
514
|
.mul(weight)
|
|
517
515
|
.div(SPOT_MARKET_WEIGHT_PRECISION);
|
|
518
516
|
|
|
@@ -1064,7 +1062,7 @@ export class ClearingHouseUser {
|
|
|
1064
1062
|
const proposedPerpPosition: PerpPosition = {
|
|
1065
1063
|
marketIndex: perpPosition.marketIndex,
|
|
1066
1064
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
1067
|
-
remainderBaseAssetAmount:
|
|
1065
|
+
remainderBaseAssetAmount: 0,
|
|
1068
1066
|
quoteAssetAmount: new BN(0),
|
|
1069
1067
|
lastCumulativeFundingRate: ZERO,
|
|
1070
1068
|
quoteEntryAmount: new BN(0),
|
|
@@ -1073,7 +1071,6 @@ export class ClearingHouseUser {
|
|
|
1073
1071
|
openAsks: new BN(0),
|
|
1074
1072
|
settledPnl: ZERO,
|
|
1075
1073
|
lpShares: ZERO,
|
|
1076
|
-
lastFeePerLp: ZERO,
|
|
1077
1074
|
lastNetBaseAssetAmountPerLp: ZERO,
|
|
1078
1075
|
lastNetQuoteAssetAmountPerLp: ZERO,
|
|
1079
1076
|
};
|
package/src/config.ts
CHANGED
|
@@ -27,7 +27,7 @@ export const configs: { [key in DriftEnv]: DriftConfig } = {
|
|
|
27
27
|
devnet: {
|
|
28
28
|
ENV: 'devnet',
|
|
29
29
|
PYTH_ORACLE_MAPPING_ADDRESS: 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2',
|
|
30
|
-
CLEARING_HOUSE_PROGRAM_ID: '
|
|
30
|
+
CLEARING_HOUSE_PROGRAM_ID: 'DUZwKJKAk2C9S88BYvQzck1M1i5hySQjxB4zW6tJ29Nw',
|
|
31
31
|
USDC_MINT_ADDRESS: '8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2',
|
|
32
32
|
PERP_MARKETS: DevnetPerpMarkets,
|
|
33
33
|
SPOT_MARKETS: DevnetSpotMarkets,
|
|
@@ -4,6 +4,13 @@ import { BN } from '../';
|
|
|
4
4
|
export const ZERO = new BN(0);
|
|
5
5
|
export const ONE = new BN(1);
|
|
6
6
|
export const TWO = new BN(2);
|
|
7
|
+
export const THREE = new BN(3);
|
|
8
|
+
export const FOUR = new BN(4);
|
|
9
|
+
export const FIVE = new BN(5);
|
|
10
|
+
export const SIX = new BN(6);
|
|
11
|
+
export const SEVEN = new BN(7);
|
|
12
|
+
export const EIGHT = new BN(8);
|
|
13
|
+
export const NINE = new BN(9);
|
|
7
14
|
export const TEN = new BN(10);
|
|
8
15
|
export const TEN_THOUSAND = new BN(10000);
|
|
9
16
|
export const BN_MAX = new BN(Number.MAX_SAFE_INTEGER);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
SPOT_MARKET_BALANCE_PRECISION_EXP,
|
|
6
6
|
LAMPORTS_EXP,
|
|
7
7
|
LAMPORTS_PRECISION,
|
|
8
|
+
SIX,
|
|
8
9
|
} from './numericConstants';
|
|
9
10
|
|
|
10
11
|
export type SpotMarketConfig = {
|
|
@@ -29,8 +30,8 @@ export const DevnetSpotMarkets: SpotMarketConfig[] = [
|
|
|
29
30
|
oracle: PublicKey.default,
|
|
30
31
|
oracleSource: OracleSource.QUOTE_ASSET,
|
|
31
32
|
mint: new PublicKey('8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2'),
|
|
32
|
-
precision:
|
|
33
|
-
precisionExp:
|
|
33
|
+
precision: new BN(10).pow(SIX),
|
|
34
|
+
precisionExp: SIX,
|
|
34
35
|
},
|
|
35
36
|
{
|
|
36
37
|
symbol: 'SOL',
|
|
@@ -48,8 +49,8 @@ export const DevnetSpotMarkets: SpotMarketConfig[] = [
|
|
|
48
49
|
oracle: new PublicKey('HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'),
|
|
49
50
|
oracleSource: OracleSource.PYTH,
|
|
50
51
|
mint: new PublicKey('3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv'),
|
|
51
|
-
precision:
|
|
52
|
-
precisionExp:
|
|
52
|
+
precision: new BN(10).pow(SIX),
|
|
53
|
+
precisionExp: SIX,
|
|
53
54
|
serumMarket: new PublicKey('AGsmbVu3MS9u68GEYABWosQQCZwmLcBHu4pWEuBYH7Za'),
|
|
54
55
|
},
|
|
55
56
|
];
|
package/src/dlob/DLOB.ts
CHANGED
|
@@ -54,7 +54,7 @@ export type NodeToTrigger = {
|
|
|
54
54
|
node: TriggerOrderNode;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
type Side = 'ask' | 'bid' | 'both';
|
|
57
|
+
type Side = 'ask' | 'bid' | 'both' | 'nocross';
|
|
58
58
|
|
|
59
59
|
export class DLOB {
|
|
60
60
|
openOrders = new Map<MarketTypeStr, Set<string>>();
|
|
@@ -135,6 +135,32 @@ export class DLOB {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
public clear() {
|
|
139
|
+
for (const marketType of this.openOrders.keys()) {
|
|
140
|
+
this.openOrders.get(marketType).clear();
|
|
141
|
+
}
|
|
142
|
+
this.openOrders.clear();
|
|
143
|
+
|
|
144
|
+
for (const marketType of this.orderLists.keys()) {
|
|
145
|
+
for (const marketIndex of this.orderLists.get(marketType).keys()) {
|
|
146
|
+
const marketNodeLists = this.orderLists
|
|
147
|
+
.get(marketType)
|
|
148
|
+
.get(marketIndex);
|
|
149
|
+
for (const side of Object.keys(marketNodeLists)) {
|
|
150
|
+
for (const orderType of Object.keys(marketNodeLists[side])) {
|
|
151
|
+
marketNodeLists[side][orderType].clear();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
this.orderLists.clear();
|
|
157
|
+
|
|
158
|
+
for (const marketType of this.marketIndexToAccount.keys()) {
|
|
159
|
+
this.marketIndexToAccount.get(marketType).clear();
|
|
160
|
+
}
|
|
161
|
+
this.marketIndexToAccount.clear();
|
|
162
|
+
}
|
|
163
|
+
|
|
138
164
|
/**
|
|
139
165
|
* initializes a new DLOB instance
|
|
140
166
|
*
|
|
@@ -239,7 +265,7 @@ export class DLOB {
|
|
|
239
265
|
type = 'trigger';
|
|
240
266
|
} else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
|
|
241
267
|
type = 'market';
|
|
242
|
-
} else if (order.oraclePriceOffset.
|
|
268
|
+
} else if (!order.oraclePriceOffset.eq(ZERO)) {
|
|
243
269
|
type = 'floatingLimit';
|
|
244
270
|
} else {
|
|
245
271
|
type = 'limit';
|
|
@@ -274,26 +300,35 @@ export class DLOB {
|
|
|
274
300
|
// Find all the crossing nodes
|
|
275
301
|
const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
|
|
276
302
|
marketIndex,
|
|
277
|
-
vBid,
|
|
278
|
-
vAsk,
|
|
279
303
|
slot,
|
|
280
304
|
marketType,
|
|
281
305
|
oraclePriceData
|
|
282
306
|
);
|
|
283
307
|
|
|
308
|
+
const vAMMCrossingNodesToFill: Array<NodeToFill> =
|
|
309
|
+
this.findvAMMCrossingNodesToFill(
|
|
310
|
+
marketIndex,
|
|
311
|
+
vBid,
|
|
312
|
+
vAsk,
|
|
313
|
+
slot,
|
|
314
|
+
marketType,
|
|
315
|
+
oraclePriceData
|
|
316
|
+
);
|
|
317
|
+
|
|
284
318
|
// get expired market nodes
|
|
285
|
-
const marketNodesToFill = this.
|
|
319
|
+
const marketNodesToFill = this.findExpiredMarketNodesToFill(
|
|
286
320
|
marketIndex,
|
|
287
321
|
slot,
|
|
288
322
|
marketType
|
|
289
323
|
);
|
|
290
|
-
return crossingNodesToFill.concat(
|
|
324
|
+
return crossingNodesToFill.concat(
|
|
325
|
+
vAMMCrossingNodesToFill,
|
|
326
|
+
marketNodesToFill
|
|
327
|
+
);
|
|
291
328
|
}
|
|
292
329
|
|
|
293
330
|
public findCrossingNodesToFill(
|
|
294
331
|
marketIndex: number,
|
|
295
|
-
vBid: BN | undefined,
|
|
296
|
-
vAsk: BN | undefined,
|
|
297
332
|
slot: number,
|
|
298
333
|
marketType: MarketType,
|
|
299
334
|
oraclePriceData: OraclePriceData
|
|
@@ -302,14 +337,14 @@ export class DLOB {
|
|
|
302
337
|
|
|
303
338
|
const askGenerator = this.getAsks(
|
|
304
339
|
marketIndex,
|
|
305
|
-
|
|
340
|
+
undefined, // dont include vask
|
|
306
341
|
slot,
|
|
307
342
|
marketType,
|
|
308
343
|
oraclePriceData
|
|
309
344
|
);
|
|
310
345
|
const bidGenerator = this.getBids(
|
|
311
346
|
marketIndex,
|
|
312
|
-
|
|
347
|
+
undefined, // dont include vbid
|
|
313
348
|
slot,
|
|
314
349
|
marketType,
|
|
315
350
|
oraclePriceData
|
|
@@ -334,26 +369,86 @@ export class DLOB {
|
|
|
334
369
|
} else if (exhaustedSide === 'both') {
|
|
335
370
|
nextBid = bidGenerator.next();
|
|
336
371
|
nextAsk = askGenerator.next();
|
|
372
|
+
} else if (exhaustedSide === 'nocross') {
|
|
373
|
+
break;
|
|
337
374
|
} else {
|
|
338
375
|
console.error(`invalid exhaustedSide: ${exhaustedSide}`);
|
|
339
376
|
break;
|
|
340
377
|
}
|
|
341
378
|
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
379
|
+
for (const crossingNode of crossingNodes) {
|
|
380
|
+
nodesToFill.push(crossingNode);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return nodesToFill;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
public findvAMMCrossingNodesToFill(
|
|
387
|
+
marketIndex: number,
|
|
388
|
+
vBid: BN,
|
|
389
|
+
vAsk: BN,
|
|
390
|
+
slot: number,
|
|
391
|
+
marketType: MarketType,
|
|
392
|
+
oraclePriceData: OraclePriceData
|
|
393
|
+
): NodeToFill[] {
|
|
394
|
+
const nodesToFill = new Array<NodeToFill>();
|
|
395
|
+
|
|
396
|
+
const askGenerator = this.getAsks(
|
|
397
|
+
marketIndex,
|
|
398
|
+
undefined, // dont include vask
|
|
399
|
+
slot,
|
|
400
|
+
marketType,
|
|
401
|
+
oraclePriceData
|
|
402
|
+
);
|
|
403
|
+
const bidGenerator = this.getBids(
|
|
404
|
+
marketIndex,
|
|
405
|
+
undefined, // dont include vbid
|
|
406
|
+
slot,
|
|
407
|
+
marketType,
|
|
408
|
+
oraclePriceData
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
let nextAsk = askGenerator.next();
|
|
412
|
+
let nextBid = bidGenerator.next();
|
|
413
|
+
|
|
414
|
+
// check for asks that cross vBid
|
|
415
|
+
while (!nextAsk.done) {
|
|
416
|
+
const askNode = nextAsk.value;
|
|
417
|
+
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
418
|
+
|
|
419
|
+
if (askPrice.lte(vBid) && isAuctionComplete(askNode.order, slot)) {
|
|
420
|
+
nodesToFill.push({
|
|
421
|
+
node: askNode,
|
|
422
|
+
makerNode: undefined, // filled by vAMM
|
|
423
|
+
});
|
|
424
|
+
} else {
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
nextAsk = askGenerator.next();
|
|
429
|
+
}
|
|
347
430
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
431
|
+
// check for bids that cross vAsk
|
|
432
|
+
while (!nextBid.done) {
|
|
433
|
+
const bidNode = nextBid.value;
|
|
434
|
+
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
435
|
+
|
|
436
|
+
if (bidPrice.gte(vAsk) && isAuctionComplete(bidNode.order, slot)) {
|
|
437
|
+
nodesToFill.push({
|
|
438
|
+
node: bidNode,
|
|
439
|
+
makerNode: undefined, // filled by vAMM
|
|
440
|
+
});
|
|
441
|
+
} else {
|
|
442
|
+
break;
|
|
351
443
|
}
|
|
444
|
+
|
|
445
|
+
nextBid = bidGenerator.next();
|
|
352
446
|
}
|
|
447
|
+
|
|
353
448
|
return nodesToFill;
|
|
354
449
|
}
|
|
355
450
|
|
|
356
|
-
public
|
|
451
|
+
public findExpiredMarketNodesToFill(
|
|
357
452
|
marketIndex: number,
|
|
358
453
|
slot: number,
|
|
359
454
|
marketType: MarketType
|
|
@@ -361,10 +456,7 @@ export class DLOB {
|
|
|
361
456
|
const nodesToFill = new Array<NodeToFill>();
|
|
362
457
|
// Then see if there are orders to fill against vamm
|
|
363
458
|
for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
|
|
364
|
-
if (
|
|
365
|
-
isAuctionComplete(marketBid.order, slot) ||
|
|
366
|
-
isOrderExpired(marketBid.order, slot)
|
|
367
|
-
) {
|
|
459
|
+
if (isOrderExpired(marketBid.order, slot)) {
|
|
368
460
|
nodesToFill.push({
|
|
369
461
|
node: marketBid,
|
|
370
462
|
});
|
|
@@ -372,10 +464,7 @@ export class DLOB {
|
|
|
372
464
|
}
|
|
373
465
|
|
|
374
466
|
for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
|
|
375
|
-
if (
|
|
376
|
-
isAuctionComplete(marketAsk.order, slot) ||
|
|
377
|
-
isOrderExpired(marketAsk.order, slot)
|
|
378
|
-
) {
|
|
467
|
+
if (isOrderExpired(marketAsk.order, slot)) {
|
|
379
468
|
nodesToFill.push({
|
|
380
469
|
node: marketAsk,
|
|
381
470
|
});
|
|
@@ -450,9 +539,12 @@ export class DLOB {
|
|
|
450
539
|
nodeLists.market.ask.getGenerator(),
|
|
451
540
|
];
|
|
452
541
|
|
|
453
|
-
if (marketTypeStr === 'perp') {
|
|
542
|
+
if (marketTypeStr === 'perp' && vAsk) {
|
|
454
543
|
generatorList.push(getVammNodeGenerator(vAsk));
|
|
455
544
|
}
|
|
545
|
+
if (generatorList.length === 0) {
|
|
546
|
+
throw new Error('No ask generators found');
|
|
547
|
+
}
|
|
456
548
|
|
|
457
549
|
const askGenerators = generatorList.map((generator) => {
|
|
458
550
|
return {
|
|
@@ -489,6 +581,12 @@ export class DLOB {
|
|
|
489
581
|
);
|
|
490
582
|
|
|
491
583
|
if (!bestGenerator.next.done) {
|
|
584
|
+
// skip this node if it's already completely filled
|
|
585
|
+
if (bestGenerator.next.value.isBaseFilled()) {
|
|
586
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
|
|
492
590
|
yield bestGenerator.next.value;
|
|
493
591
|
bestGenerator.next = bestGenerator.generator.next();
|
|
494
592
|
} else {
|
|
@@ -516,9 +614,13 @@ export class DLOB {
|
|
|
516
614
|
nodeLists.floatingLimit.bid.getGenerator(),
|
|
517
615
|
nodeLists.market.bid.getGenerator(),
|
|
518
616
|
];
|
|
519
|
-
if (marketTypeStr === 'perp') {
|
|
617
|
+
if (marketTypeStr === 'perp' && vBid) {
|
|
520
618
|
generatorList.push(getVammNodeGenerator(vBid));
|
|
521
619
|
}
|
|
620
|
+
if (generatorList.length === 0) {
|
|
621
|
+
throw new Error('No bid generators found');
|
|
622
|
+
}
|
|
623
|
+
|
|
522
624
|
const bidGenerators = generatorList.map((generator) => {
|
|
523
625
|
return {
|
|
524
626
|
next: generator.next(),
|
|
@@ -554,6 +656,12 @@ export class DLOB {
|
|
|
554
656
|
);
|
|
555
657
|
|
|
556
658
|
if (!bestGenerator.next.done) {
|
|
659
|
+
// skip this node if it's already completely filled
|
|
660
|
+
if (bestGenerator.next.value.isBaseFilled()) {
|
|
661
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
|
|
557
665
|
yield bestGenerator.next.value;
|
|
558
666
|
bestGenerator.next = bestGenerator.generator.next();
|
|
559
667
|
} else {
|
|
@@ -568,63 +676,32 @@ export class DLOB {
|
|
|
568
676
|
oraclePriceData: OraclePriceData,
|
|
569
677
|
slot: number
|
|
570
678
|
): {
|
|
571
|
-
crossingNodes
|
|
572
|
-
exhaustedSide
|
|
679
|
+
crossingNodes: NodeToFill[];
|
|
680
|
+
exhaustedSide: Side;
|
|
573
681
|
} {
|
|
574
682
|
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
575
683
|
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
576
684
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const containsMarketOrder =
|
|
581
|
-
(askOrder && isVariant(askOrder.orderType, 'market')) ||
|
|
582
|
-
(bidOrder && isVariant(bidOrder.orderType, 'market'));
|
|
583
|
-
|
|
584
|
-
if (!containsMarketOrder || bidPrice.lt(askPrice)) {
|
|
585
|
-
// no market orders, and no crossing orders - return
|
|
586
|
-
if (askNode.isVammNode() && !bidNode.isVammNode()) {
|
|
587
|
-
return {
|
|
588
|
-
exhaustedSide: 'bid',
|
|
589
|
-
};
|
|
590
|
-
} else if (!askNode.isVammNode() && bidNode.isVammNode()) {
|
|
591
|
-
return {
|
|
592
|
-
exhaustedSide: 'ask',
|
|
593
|
-
};
|
|
594
|
-
} else {
|
|
595
|
-
return {
|
|
596
|
-
exhaustedSide: 'both',
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// User bid crosses the vamm ask
|
|
602
|
-
if (askNode.isVammNode()) {
|
|
603
|
-
if (!isAuctionComplete(bidOrder, slot)) {
|
|
604
|
-
return {
|
|
605
|
-
exhaustedSide: 'bid',
|
|
606
|
-
};
|
|
607
|
-
}
|
|
685
|
+
// orders don't cross - we're done walkin gup the book
|
|
686
|
+
if (bidPrice.lt(askPrice)) {
|
|
608
687
|
return {
|
|
609
|
-
crossingNodes:
|
|
610
|
-
|
|
611
|
-
},
|
|
612
|
-
exhaustedSide: 'bid',
|
|
688
|
+
crossingNodes: [],
|
|
689
|
+
exhaustedSide: 'nocross',
|
|
613
690
|
};
|
|
614
691
|
}
|
|
615
692
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
693
|
+
const bidOrder = bidNode.order;
|
|
694
|
+
const askOrder = askNode.order;
|
|
695
|
+
|
|
696
|
+
// Can't match two maker orders or if maker and taker are the same
|
|
697
|
+
const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
|
|
698
|
+
if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
|
|
699
|
+
// don't have a principle way to pick which one to exhaust,
|
|
700
|
+
// exhaust each one 50% of the time so we can try each one against other orders
|
|
701
|
+
const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
|
|
623
702
|
return {
|
|
624
|
-
crossingNodes:
|
|
625
|
-
|
|
626
|
-
},
|
|
627
|
-
exhaustedSide: 'ask',
|
|
703
|
+
crossingNodes: [],
|
|
704
|
+
exhaustedSide,
|
|
628
705
|
};
|
|
629
706
|
}
|
|
630
707
|
|
|
@@ -644,34 +721,71 @@ export class DLOB {
|
|
|
644
721
|
exhaustedSide = 'bid';
|
|
645
722
|
}
|
|
646
723
|
|
|
647
|
-
//
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// update the orders as if they fill - so we don't try to match them on the next iteration
|
|
724
|
+
// update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
|
|
725
|
+
// NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
|
|
726
|
+
// then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
|
|
727
|
+
// a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
|
|
728
|
+
// than updating the existing DLOB based on events.
|
|
655
729
|
if (exhaustedSide === 'ask') {
|
|
656
|
-
|
|
730
|
+
// bid partially filled
|
|
731
|
+
const newBidOrder = { ...bidOrder };
|
|
732
|
+
newBidOrder.baseAssetAmountFilled =
|
|
657
733
|
bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
|
|
658
|
-
|
|
734
|
+
this.getListForOrder(newBidOrder).update(
|
|
735
|
+
newBidOrder,
|
|
736
|
+
bidNode.userAccount
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// ask completely filled
|
|
740
|
+
const newAskOrder = { ...askOrder };
|
|
741
|
+
newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
|
|
742
|
+
this.getListForOrder(newAskOrder).update(
|
|
743
|
+
newAskOrder,
|
|
744
|
+
askNode.userAccount
|
|
745
|
+
);
|
|
659
746
|
} else if (exhaustedSide === 'bid') {
|
|
660
|
-
|
|
747
|
+
// ask partially filled
|
|
748
|
+
const newAskOrder = { ...askOrder };
|
|
749
|
+
newAskOrder.baseAssetAmountFilled =
|
|
661
750
|
askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
|
|
662
|
-
|
|
751
|
+
this.getListForOrder(newAskOrder).update(
|
|
752
|
+
newAskOrder,
|
|
753
|
+
askNode.userAccount
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
// bid completely filled
|
|
757
|
+
const newBidOrder = { ...bidOrder };
|
|
758
|
+
newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
|
|
759
|
+
this.getListForOrder(newBidOrder).update(
|
|
760
|
+
newBidOrder,
|
|
761
|
+
bidNode.userAccount
|
|
762
|
+
);
|
|
663
763
|
} else {
|
|
664
|
-
|
|
665
|
-
|
|
764
|
+
// both completely filled
|
|
765
|
+
const newBidOrder = { ...bidOrder };
|
|
766
|
+
newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
|
|
767
|
+
this.getListForOrder(newBidOrder).update(
|
|
768
|
+
newBidOrder,
|
|
769
|
+
bidNode.userAccount
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
const newAskOrder = { ...askOrder };
|
|
773
|
+
newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
|
|
774
|
+
this.getListForOrder(newAskOrder).update(
|
|
775
|
+
newAskOrder,
|
|
776
|
+
askNode.userAccount
|
|
777
|
+
);
|
|
666
778
|
}
|
|
667
779
|
|
|
668
780
|
// Bid is maker
|
|
669
781
|
if (bidOrder.postOnly) {
|
|
670
782
|
return {
|
|
671
|
-
crossingNodes:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
783
|
+
crossingNodes: [
|
|
784
|
+
{
|
|
785
|
+
node: askNode,
|
|
786
|
+
makerNode: bidNode,
|
|
787
|
+
},
|
|
788
|
+
],
|
|
675
789
|
exhaustedSide,
|
|
676
790
|
};
|
|
677
791
|
}
|
|
@@ -679,10 +793,12 @@ export class DLOB {
|
|
|
679
793
|
// Ask is maker
|
|
680
794
|
if (askOrder.postOnly) {
|
|
681
795
|
return {
|
|
682
|
-
crossingNodes:
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
796
|
+
crossingNodes: [
|
|
797
|
+
{
|
|
798
|
+
node: bidNode,
|
|
799
|
+
makerNode: askNode,
|
|
800
|
+
},
|
|
801
|
+
],
|
|
686
802
|
exhaustedSide,
|
|
687
803
|
};
|
|
688
804
|
}
|
|
@@ -693,10 +809,12 @@ export class DLOB {
|
|
|
693
809
|
? [askNode, bidNode]
|
|
694
810
|
: [bidNode, askNode];
|
|
695
811
|
return {
|
|
696
|
-
crossingNodes:
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
812
|
+
crossingNodes: [
|
|
813
|
+
{
|
|
814
|
+
node: newerNode,
|
|
815
|
+
makerNode: olderNode,
|
|
816
|
+
},
|
|
817
|
+
],
|
|
700
818
|
exhaustedSide,
|
|
701
819
|
};
|
|
702
820
|
}
|
package/src/dlob/DLOBNode.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface DLOBNode {
|
|
|
19
19
|
getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
|
|
20
20
|
isVammNode(): boolean;
|
|
21
21
|
order: Order | undefined;
|
|
22
|
+
isBaseFilled(): boolean;
|
|
22
23
|
haveFilled: boolean;
|
|
23
24
|
userAccount: PublicKey | undefined;
|
|
24
25
|
market: SpotMarketAccount | PerpMarketAccount;
|
|
@@ -87,6 +88,10 @@ export abstract class OrderNode implements DLOBNode {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
isBaseFilled(): boolean {
|
|
92
|
+
return this.order.baseAssetAmountFilled.eq(this.order.baseAssetAmount);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
isVammNode(): boolean {
|
|
91
96
|
return false;
|
|
92
97
|
}
|
package/src/dlob/NodeList.ts
CHANGED
|
@@ -11,7 +11,10 @@ import { createNode, DLOBNode, DLOBNodeMap } from './DLOBNode';
|
|
|
11
11
|
|
|
12
12
|
export type SortDirection = 'asc' | 'desc';
|
|
13
13
|
|
|
14
|
-
export function getOrderSignature(
|
|
14
|
+
export function getOrderSignature(
|
|
15
|
+
orderId: number,
|
|
16
|
+
userAccount: PublicKey
|
|
17
|
+
): string {
|
|
15
18
|
return `${userAccount.toString()}-${orderId.toString()}`;
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -31,6 +34,12 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
|
|
|
31
34
|
private sortDirection: SortDirection
|
|
32
35
|
) {}
|
|
33
36
|
|
|
37
|
+
public clear() {
|
|
38
|
+
this.head = undefined;
|
|
39
|
+
this.length = 0;
|
|
40
|
+
this.nodeMap.clear();
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
public insert(
|
|
35
44
|
order: Order,
|
|
36
45
|
marketType: MarketTypeStr,
|
|
@@ -49,11 +58,11 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
|
|
|
49
58
|
|
|
50
59
|
const newNode = createNode(this.nodeType, order, market, userAccount);
|
|
51
60
|
|
|
52
|
-
const
|
|
53
|
-
if (this.nodeMap.has(
|
|
61
|
+
const orderSignature = getOrderSignature(order.orderId, userAccount);
|
|
62
|
+
if (this.nodeMap.has(orderSignature)) {
|
|
54
63
|
return;
|
|
55
64
|
}
|
|
56
|
-
this.nodeMap.set(
|
|
65
|
+
this.nodeMap.set(orderSignature, newNode);
|
|
57
66
|
|
|
58
67
|
this.length += 1;
|
|
59
68
|
|
|
@@ -126,7 +135,7 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
|
|
|
126
135
|
node.previous.next = node.next;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
if (this.head && node.order.orderId
|
|
138
|
+
if (this.head && node.order.orderId === this.head.order.orderId) {
|
|
130
139
|
this.head = node.next;
|
|
131
140
|
}
|
|
132
141
|
|
|
@@ -180,6 +189,7 @@ export function* getVammNodeGenerator(
|
|
|
180
189
|
order: undefined,
|
|
181
190
|
market: undefined,
|
|
182
191
|
userAccount: undefined,
|
|
192
|
+
isBaseFilled: () => false,
|
|
183
193
|
haveFilled: false,
|
|
184
194
|
};
|
|
185
195
|
}
|