@drift-labs/sdk 0.2.0-master.30 → 0.2.0-master.31
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 +6 -5
- package/lib/dlob/DLOB.js +130 -88
- package/lib/dlob/DLOBNode.d.ts +2 -0
- package/lib/dlob/DLOBNode.js +3 -0
- package/lib/dlob/NodeList.d.ts +1 -1
- package/lib/dlob/NodeList.js +5 -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 +117 -40
- package/lib/types.js +33 -3
- 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 +188 -103
- package/src/dlob/DLOBNode.ts +5 -0
- package/src/dlob/NodeList.ts +9 -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 +81 -40
- package/tests/bn/test.ts +12 -7
- package/tests/dlob/helpers.ts +56 -33
- package/tests/dlob/test.ts +1227 -404
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>>();
|
|
@@ -239,7 +239,7 @@ export class DLOB {
|
|
|
239
239
|
type = 'trigger';
|
|
240
240
|
} else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
|
|
241
241
|
type = 'market';
|
|
242
|
-
} else if (order.oraclePriceOffset.
|
|
242
|
+
} else if (!order.oraclePriceOffset.eq(ZERO)) {
|
|
243
243
|
type = 'floatingLimit';
|
|
244
244
|
} else {
|
|
245
245
|
type = 'limit';
|
|
@@ -274,26 +274,35 @@ export class DLOB {
|
|
|
274
274
|
// Find all the crossing nodes
|
|
275
275
|
const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
|
|
276
276
|
marketIndex,
|
|
277
|
-
vBid,
|
|
278
|
-
vAsk,
|
|
279
277
|
slot,
|
|
280
278
|
marketType,
|
|
281
279
|
oraclePriceData
|
|
282
280
|
);
|
|
283
281
|
|
|
282
|
+
const vAMMCrossingNodesToFill: Array<NodeToFill> =
|
|
283
|
+
this.findvAMMCrossingNodesToFill(
|
|
284
|
+
marketIndex,
|
|
285
|
+
vBid,
|
|
286
|
+
vAsk,
|
|
287
|
+
slot,
|
|
288
|
+
marketType,
|
|
289
|
+
oraclePriceData
|
|
290
|
+
);
|
|
291
|
+
|
|
284
292
|
// get expired market nodes
|
|
285
|
-
const marketNodesToFill = this.
|
|
293
|
+
const marketNodesToFill = this.findExpiredMarketNodesToFill(
|
|
286
294
|
marketIndex,
|
|
287
295
|
slot,
|
|
288
296
|
marketType
|
|
289
297
|
);
|
|
290
|
-
return crossingNodesToFill.concat(
|
|
298
|
+
return crossingNodesToFill.concat(
|
|
299
|
+
vAMMCrossingNodesToFill,
|
|
300
|
+
marketNodesToFill
|
|
301
|
+
);
|
|
291
302
|
}
|
|
292
303
|
|
|
293
304
|
public findCrossingNodesToFill(
|
|
294
305
|
marketIndex: number,
|
|
295
|
-
vBid: BN | undefined,
|
|
296
|
-
vAsk: BN | undefined,
|
|
297
306
|
slot: number,
|
|
298
307
|
marketType: MarketType,
|
|
299
308
|
oraclePriceData: OraclePriceData
|
|
@@ -302,14 +311,14 @@ export class DLOB {
|
|
|
302
311
|
|
|
303
312
|
const askGenerator = this.getAsks(
|
|
304
313
|
marketIndex,
|
|
305
|
-
|
|
314
|
+
undefined, // dont include vask
|
|
306
315
|
slot,
|
|
307
316
|
marketType,
|
|
308
317
|
oraclePriceData
|
|
309
318
|
);
|
|
310
319
|
const bidGenerator = this.getBids(
|
|
311
320
|
marketIndex,
|
|
312
|
-
|
|
321
|
+
undefined, // dont include vbid
|
|
313
322
|
slot,
|
|
314
323
|
marketType,
|
|
315
324
|
oraclePriceData
|
|
@@ -334,26 +343,86 @@ export class DLOB {
|
|
|
334
343
|
} else if (exhaustedSide === 'both') {
|
|
335
344
|
nextBid = bidGenerator.next();
|
|
336
345
|
nextAsk = askGenerator.next();
|
|
346
|
+
} else if (exhaustedSide === 'nocross') {
|
|
347
|
+
break;
|
|
337
348
|
} else {
|
|
338
349
|
console.error(`invalid exhaustedSide: ${exhaustedSide}`);
|
|
339
350
|
break;
|
|
340
351
|
}
|
|
341
352
|
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
353
|
+
for (const crossingNode of crossingNodes) {
|
|
354
|
+
nodesToFill.push(crossingNode);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return nodesToFill;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public findvAMMCrossingNodesToFill(
|
|
361
|
+
marketIndex: number,
|
|
362
|
+
vBid: BN,
|
|
363
|
+
vAsk: BN,
|
|
364
|
+
slot: number,
|
|
365
|
+
marketType: MarketType,
|
|
366
|
+
oraclePriceData: OraclePriceData
|
|
367
|
+
): NodeToFill[] {
|
|
368
|
+
const nodesToFill = new Array<NodeToFill>();
|
|
347
369
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
370
|
+
const askGenerator = this.getAsks(
|
|
371
|
+
marketIndex,
|
|
372
|
+
undefined, // dont include vask
|
|
373
|
+
slot,
|
|
374
|
+
marketType,
|
|
375
|
+
oraclePriceData
|
|
376
|
+
);
|
|
377
|
+
const bidGenerator = this.getBids(
|
|
378
|
+
marketIndex,
|
|
379
|
+
undefined, // dont include vbid
|
|
380
|
+
slot,
|
|
381
|
+
marketType,
|
|
382
|
+
oraclePriceData
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
let nextAsk = askGenerator.next();
|
|
386
|
+
let nextBid = bidGenerator.next();
|
|
387
|
+
|
|
388
|
+
// check for asks that cross vBid
|
|
389
|
+
while (!nextAsk.done) {
|
|
390
|
+
const askNode = nextAsk.value;
|
|
391
|
+
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
392
|
+
|
|
393
|
+
if (askPrice.lte(vBid) && isAuctionComplete(askNode.order, slot)) {
|
|
394
|
+
nodesToFill.push({
|
|
395
|
+
node: askNode,
|
|
396
|
+
makerNode: undefined, // filled by vAMM
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
break;
|
|
351
400
|
}
|
|
401
|
+
|
|
402
|
+
nextAsk = askGenerator.next();
|
|
352
403
|
}
|
|
404
|
+
|
|
405
|
+
// check for bids that cross vAsk
|
|
406
|
+
while (!nextBid.done) {
|
|
407
|
+
const bidNode = nextBid.value;
|
|
408
|
+
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
409
|
+
|
|
410
|
+
if (bidPrice.gte(vAsk) && isAuctionComplete(bidNode.order, slot)) {
|
|
411
|
+
nodesToFill.push({
|
|
412
|
+
node: bidNode,
|
|
413
|
+
makerNode: undefined, // filled by vAMM
|
|
414
|
+
});
|
|
415
|
+
} else {
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
nextBid = bidGenerator.next();
|
|
420
|
+
}
|
|
421
|
+
|
|
353
422
|
return nodesToFill;
|
|
354
423
|
}
|
|
355
424
|
|
|
356
|
-
public
|
|
425
|
+
public findExpiredMarketNodesToFill(
|
|
357
426
|
marketIndex: number,
|
|
358
427
|
slot: number,
|
|
359
428
|
marketType: MarketType
|
|
@@ -361,10 +430,7 @@ export class DLOB {
|
|
|
361
430
|
const nodesToFill = new Array<NodeToFill>();
|
|
362
431
|
// Then see if there are orders to fill against vamm
|
|
363
432
|
for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
|
|
364
|
-
if (
|
|
365
|
-
isAuctionComplete(marketBid.order, slot) ||
|
|
366
|
-
isOrderExpired(marketBid.order, slot)
|
|
367
|
-
) {
|
|
433
|
+
if (isOrderExpired(marketBid.order, slot)) {
|
|
368
434
|
nodesToFill.push({
|
|
369
435
|
node: marketBid,
|
|
370
436
|
});
|
|
@@ -372,10 +438,7 @@ export class DLOB {
|
|
|
372
438
|
}
|
|
373
439
|
|
|
374
440
|
for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
|
|
375
|
-
if (
|
|
376
|
-
isAuctionComplete(marketAsk.order, slot) ||
|
|
377
|
-
isOrderExpired(marketAsk.order, slot)
|
|
378
|
-
) {
|
|
441
|
+
if (isOrderExpired(marketAsk.order, slot)) {
|
|
379
442
|
nodesToFill.push({
|
|
380
443
|
node: marketAsk,
|
|
381
444
|
});
|
|
@@ -450,7 +513,7 @@ export class DLOB {
|
|
|
450
513
|
nodeLists.market.ask.getGenerator(),
|
|
451
514
|
];
|
|
452
515
|
|
|
453
|
-
if (marketTypeStr === 'perp') {
|
|
516
|
+
if (marketTypeStr === 'perp' && vAsk) {
|
|
454
517
|
generatorList.push(getVammNodeGenerator(vAsk));
|
|
455
518
|
}
|
|
456
519
|
|
|
@@ -489,6 +552,12 @@ export class DLOB {
|
|
|
489
552
|
);
|
|
490
553
|
|
|
491
554
|
if (!bestGenerator.next.done) {
|
|
555
|
+
// skip this node if it's already completely filled
|
|
556
|
+
if (bestGenerator.next.value.isBaseFilled()) {
|
|
557
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
|
|
492
561
|
yield bestGenerator.next.value;
|
|
493
562
|
bestGenerator.next = bestGenerator.generator.next();
|
|
494
563
|
} else {
|
|
@@ -516,7 +585,7 @@ export class DLOB {
|
|
|
516
585
|
nodeLists.floatingLimit.bid.getGenerator(),
|
|
517
586
|
nodeLists.market.bid.getGenerator(),
|
|
518
587
|
];
|
|
519
|
-
if (marketTypeStr === 'perp') {
|
|
588
|
+
if (marketTypeStr === 'perp' && vBid) {
|
|
520
589
|
generatorList.push(getVammNodeGenerator(vBid));
|
|
521
590
|
}
|
|
522
591
|
const bidGenerators = generatorList.map((generator) => {
|
|
@@ -554,6 +623,12 @@ export class DLOB {
|
|
|
554
623
|
);
|
|
555
624
|
|
|
556
625
|
if (!bestGenerator.next.done) {
|
|
626
|
+
// skip this node if it's already completely filled
|
|
627
|
+
if (bestGenerator.next.value.isBaseFilled()) {
|
|
628
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
|
|
557
632
|
yield bestGenerator.next.value;
|
|
558
633
|
bestGenerator.next = bestGenerator.generator.next();
|
|
559
634
|
} else {
|
|
@@ -568,63 +643,32 @@ export class DLOB {
|
|
|
568
643
|
oraclePriceData: OraclePriceData,
|
|
569
644
|
slot: number
|
|
570
645
|
): {
|
|
571
|
-
crossingNodes
|
|
572
|
-
exhaustedSide
|
|
646
|
+
crossingNodes: NodeToFill[];
|
|
647
|
+
exhaustedSide: Side;
|
|
573
648
|
} {
|
|
574
649
|
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
575
650
|
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
576
651
|
|
|
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
|
-
}
|
|
652
|
+
// orders don't cross - we're done walkin gup the book
|
|
653
|
+
if (bidPrice.lt(askPrice)) {
|
|
608
654
|
return {
|
|
609
|
-
crossingNodes:
|
|
610
|
-
|
|
611
|
-
},
|
|
612
|
-
exhaustedSide: 'bid',
|
|
655
|
+
crossingNodes: [],
|
|
656
|
+
exhaustedSide: 'nocross',
|
|
613
657
|
};
|
|
614
658
|
}
|
|
615
659
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
660
|
+
const bidOrder = bidNode.order;
|
|
661
|
+
const askOrder = askNode.order;
|
|
662
|
+
|
|
663
|
+
// Can't match two maker orders or if maker and taker are the same
|
|
664
|
+
const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
|
|
665
|
+
if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
|
|
666
|
+
// don't have a principle way to pick which one to exhaust,
|
|
667
|
+
// exhaust each one 50% of the time so we can try each one against other orders
|
|
668
|
+
const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
|
|
623
669
|
return {
|
|
624
|
-
crossingNodes:
|
|
625
|
-
|
|
626
|
-
},
|
|
627
|
-
exhaustedSide: 'ask',
|
|
670
|
+
crossingNodes: [],
|
|
671
|
+
exhaustedSide,
|
|
628
672
|
};
|
|
629
673
|
}
|
|
630
674
|
|
|
@@ -644,34 +688,71 @@ export class DLOB {
|
|
|
644
688
|
exhaustedSide = 'bid';
|
|
645
689
|
}
|
|
646
690
|
|
|
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
|
|
691
|
+
// update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
|
|
692
|
+
// NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
|
|
693
|
+
// then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
|
|
694
|
+
// a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
|
|
695
|
+
// than updating the existing DLOB based on events.
|
|
655
696
|
if (exhaustedSide === 'ask') {
|
|
656
|
-
|
|
697
|
+
// bid partially filled
|
|
698
|
+
const newBidOrder = { ...bidOrder };
|
|
699
|
+
newBidOrder.baseAssetAmountFilled =
|
|
657
700
|
bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
|
|
658
|
-
|
|
701
|
+
this.getListForOrder(newBidOrder).update(
|
|
702
|
+
newBidOrder,
|
|
703
|
+
bidNode.userAccount
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
// ask completely filled
|
|
707
|
+
const newAskOrder = { ...askOrder };
|
|
708
|
+
newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
|
|
709
|
+
this.getListForOrder(newAskOrder).update(
|
|
710
|
+
newAskOrder,
|
|
711
|
+
askNode.userAccount
|
|
712
|
+
);
|
|
659
713
|
} else if (exhaustedSide === 'bid') {
|
|
660
|
-
|
|
714
|
+
// ask partially filled
|
|
715
|
+
const newAskOrder = { ...askOrder };
|
|
716
|
+
newAskOrder.baseAssetAmountFilled =
|
|
661
717
|
askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
|
|
662
|
-
|
|
718
|
+
this.getListForOrder(newAskOrder).update(
|
|
719
|
+
newAskOrder,
|
|
720
|
+
askNode.userAccount
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
// bid completely filled
|
|
724
|
+
const newBidOrder = { ...bidOrder };
|
|
725
|
+
newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
|
|
726
|
+
this.getListForOrder(newBidOrder).update(
|
|
727
|
+
newBidOrder,
|
|
728
|
+
bidNode.userAccount
|
|
729
|
+
);
|
|
663
730
|
} else {
|
|
664
|
-
|
|
665
|
-
|
|
731
|
+
// both completely filled
|
|
732
|
+
const newBidOrder = { ...bidOrder };
|
|
733
|
+
newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
|
|
734
|
+
this.getListForOrder(newBidOrder).update(
|
|
735
|
+
newBidOrder,
|
|
736
|
+
bidNode.userAccount
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
const newAskOrder = { ...askOrder };
|
|
740
|
+
newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
|
|
741
|
+
this.getListForOrder(newAskOrder).update(
|
|
742
|
+
newAskOrder,
|
|
743
|
+
askNode.userAccount
|
|
744
|
+
);
|
|
666
745
|
}
|
|
667
746
|
|
|
668
747
|
// Bid is maker
|
|
669
748
|
if (bidOrder.postOnly) {
|
|
670
749
|
return {
|
|
671
|
-
crossingNodes:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
750
|
+
crossingNodes: [
|
|
751
|
+
{
|
|
752
|
+
node: askNode,
|
|
753
|
+
makerNode: bidNode,
|
|
754
|
+
},
|
|
755
|
+
],
|
|
675
756
|
exhaustedSide,
|
|
676
757
|
};
|
|
677
758
|
}
|
|
@@ -679,10 +760,12 @@ export class DLOB {
|
|
|
679
760
|
// Ask is maker
|
|
680
761
|
if (askOrder.postOnly) {
|
|
681
762
|
return {
|
|
682
|
-
crossingNodes:
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
763
|
+
crossingNodes: [
|
|
764
|
+
{
|
|
765
|
+
node: bidNode,
|
|
766
|
+
makerNode: askNode,
|
|
767
|
+
},
|
|
768
|
+
],
|
|
686
769
|
exhaustedSide,
|
|
687
770
|
};
|
|
688
771
|
}
|
|
@@ -693,10 +776,12 @@ export class DLOB {
|
|
|
693
776
|
? [askNode, bidNode]
|
|
694
777
|
: [bidNode, askNode];
|
|
695
778
|
return {
|
|
696
|
-
crossingNodes:
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
779
|
+
crossingNodes: [
|
|
780
|
+
{
|
|
781
|
+
node: newerNode,
|
|
782
|
+
makerNode: olderNode,
|
|
783
|
+
},
|
|
784
|
+
],
|
|
700
785
|
exhaustedSide,
|
|
701
786
|
};
|
|
702
787
|
}
|
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
|
|
|
@@ -49,11 +52,11 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
|
|
|
49
52
|
|
|
50
53
|
const newNode = createNode(this.nodeType, order, market, userAccount);
|
|
51
54
|
|
|
52
|
-
const
|
|
53
|
-
if (this.nodeMap.has(
|
|
55
|
+
const orderSignature = getOrderSignature(order.orderId, userAccount);
|
|
56
|
+
if (this.nodeMap.has(orderSignature)) {
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
|
-
this.nodeMap.set(
|
|
59
|
+
this.nodeMap.set(orderSignature, newNode);
|
|
57
60
|
|
|
58
61
|
this.length += 1;
|
|
59
62
|
|
|
@@ -126,7 +129,7 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
|
|
|
126
129
|
node.previous.next = node.next;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
if (this.head && node.order.orderId
|
|
132
|
+
if (this.head && node.order.orderId === this.head.order.orderId) {
|
|
130
133
|
this.head = node.next;
|
|
131
134
|
}
|
|
132
135
|
|
|
@@ -180,6 +183,7 @@ export function* getVammNodeGenerator(
|
|
|
180
183
|
order: undefined,
|
|
181
184
|
market: undefined,
|
|
182
185
|
userAccount: undefined,
|
|
186
|
+
isBaseFilled: () => false,
|
|
183
187
|
haveFilled: false,
|
|
184
188
|
};
|
|
185
189
|
}
|
package/src/events/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
SettlePnlRecord,
|
|
11
11
|
LPRecord,
|
|
12
12
|
InsuranceFundRecord,
|
|
13
|
+
SpotInterestRecord,
|
|
13
14
|
} from '../index';
|
|
14
15
|
|
|
15
16
|
export type EventSubscriptionOptions = {
|
|
@@ -37,6 +38,7 @@ export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
|
|
|
37
38
|
'SettlePnlRecord',
|
|
38
39
|
'LPRecord',
|
|
39
40
|
'InsuranceFundRecord',
|
|
41
|
+
'SpotInterestRecord',
|
|
40
42
|
],
|
|
41
43
|
maxEventsPerType: 4096,
|
|
42
44
|
orderBy: 'blockchain',
|
|
@@ -74,6 +76,7 @@ export type EventMap = {
|
|
|
74
76
|
NewUserRecord: Event<NewUserRecord>;
|
|
75
77
|
LPRecord: Event<LPRecord>;
|
|
76
78
|
InsuranceFundRecord: Event<InsuranceFundRecord>;
|
|
79
|
+
SpotInterestRecord: Event<SpotInterestRecord>;
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
export type EventType = keyof EventMap;
|