@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/lib/clearingHouseUser.js
CHANGED
|
@@ -65,7 +65,7 @@ class ClearingHouseUser {
|
|
|
65
65
|
getEmptyPosition(marketIndex) {
|
|
66
66
|
return {
|
|
67
67
|
baseAssetAmount: numericConstants_1.ZERO,
|
|
68
|
-
remainderBaseAssetAmount:
|
|
68
|
+
remainderBaseAssetAmount: 0,
|
|
69
69
|
lastCumulativeFundingRate: numericConstants_1.ZERO,
|
|
70
70
|
marketIndex,
|
|
71
71
|
quoteAssetAmount: numericConstants_1.ZERO,
|
|
@@ -75,7 +75,6 @@ class ClearingHouseUser {
|
|
|
75
75
|
openAsks: numericConstants_1.ZERO,
|
|
76
76
|
settledPnl: numericConstants_1.ZERO,
|
|
77
77
|
lpShares: numericConstants_1.ZERO,
|
|
78
|
-
lastFeePerLp: numericConstants_1.ZERO,
|
|
79
78
|
lastNetBaseAssetAmountPerLp: numericConstants_1.ZERO,
|
|
80
79
|
lastNetQuoteAssetAmountPerLp: numericConstants_1.ZERO,
|
|
81
80
|
};
|
|
@@ -89,7 +88,7 @@ class ClearingHouseUser {
|
|
|
89
88
|
* @returns Order
|
|
90
89
|
*/
|
|
91
90
|
getOrder(orderId) {
|
|
92
|
-
return this.getUserAccount().orders.find((order) => order.orderId
|
|
91
|
+
return this.getUserAccount().orders.find((order) => order.orderId === orderId);
|
|
93
92
|
}
|
|
94
93
|
/**
|
|
95
94
|
* @param userOrderId
|
|
@@ -134,15 +133,13 @@ class ClearingHouseUser {
|
|
|
134
133
|
return [standardizedAmount, remainder];
|
|
135
134
|
}
|
|
136
135
|
const [standardizedBaa, remainderBaa] = standardize(deltaBaa, market.amm.baseAssetAmountStepSize);
|
|
137
|
-
position.remainderBaseAssetAmount
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.abs()
|
|
141
|
-
.gte(market.amm.baseAssetAmountStepSize)) {
|
|
136
|
+
position.remainderBaseAssetAmount += remainderBaa.toNumber();
|
|
137
|
+
if (Math.abs(position.remainderBaseAssetAmount) >
|
|
138
|
+
market.amm.baseAssetAmountStepSize.toNumber()) {
|
|
142
139
|
const [newStandardizedBaa, newRemainderBaa] = standardize(position.remainderBaseAssetAmount, market.amm.baseAssetAmountStepSize);
|
|
143
140
|
position.baseAssetAmount =
|
|
144
141
|
position.baseAssetAmount.add(newStandardizedBaa);
|
|
145
|
-
position.remainderBaseAssetAmount = newRemainderBaa;
|
|
142
|
+
position.remainderBaseAssetAmount = newRemainderBaa.toNumber();
|
|
146
143
|
}
|
|
147
144
|
let updateType;
|
|
148
145
|
if (position.baseAssetAmount.eq(numericConstants_1.ZERO)) {
|
|
@@ -300,7 +297,7 @@ class ClearingHouseUser {
|
|
|
300
297
|
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
301
298
|
let newTotalLiabilityValue = totalLiabilityValue;
|
|
302
299
|
if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
|
|
303
|
-
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
300
|
+
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
304
301
|
newTotalLiabilityValue =
|
|
305
302
|
newTotalLiabilityValue.add(baseLiabilityValue);
|
|
306
303
|
}
|
|
@@ -310,6 +307,7 @@ class ClearingHouseUser {
|
|
|
310
307
|
weight = _1.BN.max(weight, new _1.BN(this.getUserAccount().customMarginRatio));
|
|
311
308
|
}
|
|
312
309
|
const weightedTokenValue = worstCaseQuoteTokenAmount
|
|
310
|
+
.abs()
|
|
313
311
|
.mul(weight)
|
|
314
312
|
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
315
313
|
newTotalLiabilityValue =
|
|
@@ -615,7 +613,7 @@ class ClearingHouseUser {
|
|
|
615
613
|
const proposedPerpPosition = {
|
|
616
614
|
marketIndex: perpPosition.marketIndex,
|
|
617
615
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
618
|
-
remainderBaseAssetAmount:
|
|
616
|
+
remainderBaseAssetAmount: 0,
|
|
619
617
|
quoteAssetAmount: new _1.BN(0),
|
|
620
618
|
lastCumulativeFundingRate: numericConstants_1.ZERO,
|
|
621
619
|
quoteEntryAmount: new _1.BN(0),
|
|
@@ -624,7 +622,6 @@ class ClearingHouseUser {
|
|
|
624
622
|
openAsks: new _1.BN(0),
|
|
625
623
|
settledPnl: numericConstants_1.ZERO,
|
|
626
624
|
lpShares: numericConstants_1.ZERO,
|
|
627
|
-
lastFeePerLp: numericConstants_1.ZERO,
|
|
628
625
|
lastNetBaseAssetAmountPerLp: numericConstants_1.ZERO,
|
|
629
626
|
lastNetQuoteAssetAmountPerLp: numericConstants_1.ZERO,
|
|
630
627
|
};
|
package/lib/config.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.configs = {
|
|
|
7
7
|
devnet: {
|
|
8
8
|
ENV: 'devnet',
|
|
9
9
|
PYTH_ORACLE_MAPPING_ADDRESS: 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2',
|
|
10
|
-
CLEARING_HOUSE_PROGRAM_ID: '
|
|
10
|
+
CLEARING_HOUSE_PROGRAM_ID: 'DUZwKJKAk2C9S88BYvQzck1M1i5hySQjxB4zW6tJ29Nw',
|
|
11
11
|
USDC_MINT_ADDRESS: '8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2',
|
|
12
12
|
PERP_MARKETS: perpMarkets_1.DevnetPerpMarkets,
|
|
13
13
|
SPOT_MARKETS: spotMarkets_1.DevnetSpotMarkets,
|
|
@@ -3,6 +3,13 @@ import { BN } from '../';
|
|
|
3
3
|
export declare const ZERO: BN;
|
|
4
4
|
export declare const ONE: BN;
|
|
5
5
|
export declare const TWO: BN;
|
|
6
|
+
export declare const THREE: BN;
|
|
7
|
+
export declare const FOUR: BN;
|
|
8
|
+
export declare const FIVE: BN;
|
|
9
|
+
export declare const SIX: BN;
|
|
10
|
+
export declare const SEVEN: BN;
|
|
11
|
+
export declare const EIGHT: BN;
|
|
12
|
+
export declare const NINE: BN;
|
|
6
13
|
export declare const TEN: BN;
|
|
7
14
|
export declare const TEN_THOUSAND: BN;
|
|
8
15
|
export declare const BN_MAX: BN;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LAMPORTS_EXP = exports.LAMPORTS_PRECISION = exports.QUOTE_SPOT_MARKET_INDEX = exports.ONE_YEAR = exports.BID_ASK_SPREAD_PRECISION = exports.MARGIN_PRECISION = exports.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO = exports.PRICE_TO_QUOTE_PRECISION = exports.PRICE_DIV_PEG = exports.AMM_TO_QUOTE_PRECISION_RATIO = exports.BASE_PRECISION_EXP = exports.BASE_PRECISION = exports.AMM_RESERVE_PRECISION = exports.PEG_PRECISION = exports.FUNDING_RATE_BUFFER_PRECISION = exports.PRICE_PRECISION = exports.QUOTE_PRECISION = exports.LIQUIDATION_FEE_PRECISION = exports.SPOT_MARKET_IMF_PRECISION = exports.SPOT_MARKET_IMF_PRECISION_EXP = exports.SPOT_MARKET_BALANCE_PRECISION = exports.SPOT_MARKET_BALANCE_PRECISION_EXP = exports.SPOT_MARKET_WEIGHT_PRECISION = exports.SPOT_MARKET_UTILIZATION_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION_EXP = exports.SPOT_MARKET_RATE_PRECISION = exports.SPOT_MARKET_RATE_PRECISION_EXP = exports.AMM_RESERVE_PRECISION_EXP = exports.PEG_PRECISION_EXP = exports.FUNDING_RATE_PRECISION_EXP = exports.PRICE_PRECISION_EXP = exports.FUNDING_RATE_BUFFER_PRECISION_EXP = exports.QUOTE_PRECISION_EXP = exports.MAX_LEVERAGE = exports.TEN_MILLION = exports.BN_MAX = exports.TEN_THOUSAND = exports.TEN = exports.TWO = exports.ONE = exports.ZERO = void 0;
|
|
3
|
+
exports.LAMPORTS_EXP = exports.LAMPORTS_PRECISION = exports.QUOTE_SPOT_MARKET_INDEX = exports.ONE_YEAR = exports.BID_ASK_SPREAD_PRECISION = exports.MARGIN_PRECISION = exports.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO = exports.PRICE_TO_QUOTE_PRECISION = exports.PRICE_DIV_PEG = exports.AMM_TO_QUOTE_PRECISION_RATIO = exports.BASE_PRECISION_EXP = exports.BASE_PRECISION = exports.AMM_RESERVE_PRECISION = exports.PEG_PRECISION = exports.FUNDING_RATE_BUFFER_PRECISION = exports.PRICE_PRECISION = exports.QUOTE_PRECISION = exports.LIQUIDATION_FEE_PRECISION = exports.SPOT_MARKET_IMF_PRECISION = exports.SPOT_MARKET_IMF_PRECISION_EXP = exports.SPOT_MARKET_BALANCE_PRECISION = exports.SPOT_MARKET_BALANCE_PRECISION_EXP = exports.SPOT_MARKET_WEIGHT_PRECISION = exports.SPOT_MARKET_UTILIZATION_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION_EXP = exports.SPOT_MARKET_RATE_PRECISION = exports.SPOT_MARKET_RATE_PRECISION_EXP = exports.AMM_RESERVE_PRECISION_EXP = exports.PEG_PRECISION_EXP = exports.FUNDING_RATE_PRECISION_EXP = exports.PRICE_PRECISION_EXP = exports.FUNDING_RATE_BUFFER_PRECISION_EXP = exports.QUOTE_PRECISION_EXP = exports.MAX_LEVERAGE = exports.TEN_MILLION = exports.BN_MAX = exports.TEN_THOUSAND = exports.TEN = exports.NINE = exports.EIGHT = exports.SEVEN = exports.SIX = exports.FIVE = exports.FOUR = exports.THREE = exports.TWO = exports.ONE = exports.ZERO = void 0;
|
|
4
4
|
const web3_js_1 = require("@solana/web3.js");
|
|
5
5
|
const __1 = require("../");
|
|
6
6
|
exports.ZERO = new __1.BN(0);
|
|
7
7
|
exports.ONE = new __1.BN(1);
|
|
8
8
|
exports.TWO = new __1.BN(2);
|
|
9
|
+
exports.THREE = new __1.BN(3);
|
|
10
|
+
exports.FOUR = new __1.BN(4);
|
|
11
|
+
exports.FIVE = new __1.BN(5);
|
|
12
|
+
exports.SIX = new __1.BN(6);
|
|
13
|
+
exports.SEVEN = new __1.BN(7);
|
|
14
|
+
exports.EIGHT = new __1.BN(8);
|
|
15
|
+
exports.NINE = new __1.BN(9);
|
|
9
16
|
exports.TEN = new __1.BN(10);
|
|
10
17
|
exports.TEN_THOUSAND = new __1.BN(10000);
|
|
11
18
|
exports.BN_MAX = new __1.BN(Number.MAX_SAFE_INTEGER);
|
|
@@ -12,8 +12,8 @@ exports.DevnetSpotMarkets = [
|
|
|
12
12
|
oracle: web3_js_1.PublicKey.default,
|
|
13
13
|
oracleSource: __1.OracleSource.QUOTE_ASSET,
|
|
14
14
|
mint: new web3_js_1.PublicKey('8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2'),
|
|
15
|
-
precision: numericConstants_1.
|
|
16
|
-
precisionExp: numericConstants_1.
|
|
15
|
+
precision: new __1.BN(10).pow(numericConstants_1.SIX),
|
|
16
|
+
precisionExp: numericConstants_1.SIX,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
symbol: 'SOL',
|
|
@@ -31,8 +31,8 @@ exports.DevnetSpotMarkets = [
|
|
|
31
31
|
oracle: new web3_js_1.PublicKey('HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'),
|
|
32
32
|
oracleSource: __1.OracleSource.PYTH,
|
|
33
33
|
mint: new web3_js_1.PublicKey('3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv'),
|
|
34
|
-
precision: numericConstants_1.
|
|
35
|
-
precisionExp: numericConstants_1.
|
|
34
|
+
precision: new __1.BN(10).pow(numericConstants_1.SIX),
|
|
35
|
+
precisionExp: numericConstants_1.SIX,
|
|
36
36
|
serumMarket: new web3_js_1.PublicKey('AGsmbVu3MS9u68GEYABWosQQCZwmLcBHu4pWEuBYH7Za'),
|
|
37
37
|
},
|
|
38
38
|
];
|
package/lib/dlob/DLOB.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare type NodeToFill = {
|
|
|
29
29
|
export declare type NodeToTrigger = {
|
|
30
30
|
node: TriggerOrderNode;
|
|
31
31
|
};
|
|
32
|
-
declare type Side = 'ask' | 'bid' | 'both';
|
|
32
|
+
declare type Side = 'ask' | 'bid' | 'both' | 'nocross';
|
|
33
33
|
export declare class DLOB {
|
|
34
34
|
openOrders: Map<MarketTypeStr, Set<string>>;
|
|
35
35
|
orderLists: Map<MarketTypeStr, Map<number, MarketNodeLists>>;
|
|
@@ -43,6 +43,7 @@ export declare class DLOB {
|
|
|
43
43
|
* @param silent set to true to prevent logging on inserts and removals
|
|
44
44
|
*/
|
|
45
45
|
constructor(perpMarkets: PerpMarketAccount[], spotMarkets: SpotMarketAccount[], silent?: boolean);
|
|
46
|
+
clear(): void;
|
|
46
47
|
/**
|
|
47
48
|
* initializes a new DLOB instance
|
|
48
49
|
*
|
|
@@ -54,16 +55,17 @@ export declare class DLOB {
|
|
|
54
55
|
trigger(order: Order, userAccount: PublicKey, onTrigger?: OrderBookCallback): void;
|
|
55
56
|
getListForOrder(order: Order): NodeList<any> | undefined;
|
|
56
57
|
findNodesToFill(marketIndex: number, vBid: BN | undefined, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
|
|
57
|
-
findCrossingNodesToFill(marketIndex: number,
|
|
58
|
-
|
|
58
|
+
findCrossingNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
|
|
59
|
+
findvAMMCrossingNodesToFill(marketIndex: number, vBid: BN, vAsk: BN, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
|
|
60
|
+
findExpiredMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
|
|
59
61
|
findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
|
|
60
62
|
getMarketBids(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
|
|
61
63
|
getMarketAsks(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
|
|
62
64
|
getAsks(marketIndex: number, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
|
|
63
65
|
getBids(marketIndex: number, vBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
|
|
64
66
|
findCrossingOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
|
|
65
|
-
crossingNodes
|
|
66
|
-
exhaustedSide
|
|
67
|
+
crossingNodes: NodeToFill[];
|
|
68
|
+
exhaustedSide: Side;
|
|
67
69
|
};
|
|
68
70
|
getBestAsk(marketIndex: number, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
|
|
69
71
|
getBestBid(marketIndex: number, vBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
|
package/lib/dlob/DLOB.js
CHANGED
|
@@ -68,6 +68,29 @@ class DLOB {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
clear() {
|
|
72
|
+
for (const marketType of this.openOrders.keys()) {
|
|
73
|
+
this.openOrders.get(marketType).clear();
|
|
74
|
+
}
|
|
75
|
+
this.openOrders.clear();
|
|
76
|
+
for (const marketType of this.orderLists.keys()) {
|
|
77
|
+
for (const marketIndex of this.orderLists.get(marketType).keys()) {
|
|
78
|
+
const marketNodeLists = this.orderLists
|
|
79
|
+
.get(marketType)
|
|
80
|
+
.get(marketIndex);
|
|
81
|
+
for (const side of Object.keys(marketNodeLists)) {
|
|
82
|
+
for (const orderType of Object.keys(marketNodeLists[side])) {
|
|
83
|
+
marketNodeLists[side][orderType].clear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.orderLists.clear();
|
|
89
|
+
for (const marketType of this.marketIndexToAccount.keys()) {
|
|
90
|
+
this.marketIndexToAccount.get(marketType).clear();
|
|
91
|
+
}
|
|
92
|
+
this.marketIndexToAccount.clear();
|
|
93
|
+
}
|
|
71
94
|
/**
|
|
72
95
|
* initializes a new DLOB instance
|
|
73
96
|
*
|
|
@@ -142,7 +165,7 @@ class DLOB {
|
|
|
142
165
|
else if ((0, __1.isOneOfVariant)(order.orderType, ['market', 'triggerMarket'])) {
|
|
143
166
|
type = 'market';
|
|
144
167
|
}
|
|
145
|
-
else if (order.oraclePriceOffset.
|
|
168
|
+
else if (!order.oraclePriceOffset.eq(__1.ZERO)) {
|
|
146
169
|
type = 'floatingLimit';
|
|
147
170
|
}
|
|
148
171
|
else {
|
|
@@ -163,15 +186,18 @@ class DLOB {
|
|
|
163
186
|
}
|
|
164
187
|
findNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
|
|
165
188
|
// Find all the crossing nodes
|
|
166
|
-
const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex,
|
|
189
|
+
const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData);
|
|
190
|
+
const vAMMCrossingNodesToFill = this.findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
|
|
167
191
|
// get expired market nodes
|
|
168
|
-
const marketNodesToFill = this.
|
|
169
|
-
return crossingNodesToFill.concat(marketNodesToFill);
|
|
192
|
+
const marketNodesToFill = this.findExpiredMarketNodesToFill(marketIndex, slot, marketType);
|
|
193
|
+
return crossingNodesToFill.concat(vAMMCrossingNodesToFill, marketNodesToFill);
|
|
170
194
|
}
|
|
171
|
-
findCrossingNodesToFill(marketIndex,
|
|
195
|
+
findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData) {
|
|
172
196
|
const nodesToFill = new Array();
|
|
173
|
-
const askGenerator = this.getAsks(marketIndex,
|
|
174
|
-
|
|
197
|
+
const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
|
|
198
|
+
slot, marketType, oraclePriceData);
|
|
199
|
+
const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
|
|
200
|
+
slot, marketType, oraclePriceData);
|
|
175
201
|
let nextAsk = askGenerator.next();
|
|
176
202
|
let nextBid = bidGenerator.next();
|
|
177
203
|
// First try to find orders that cross
|
|
@@ -187,33 +213,71 @@ class DLOB {
|
|
|
187
213
|
nextBid = bidGenerator.next();
|
|
188
214
|
nextAsk = askGenerator.next();
|
|
189
215
|
}
|
|
216
|
+
else if (exhaustedSide === 'nocross') {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
190
219
|
else {
|
|
191
220
|
console.error(`invalid exhaustedSide: ${exhaustedSide}`);
|
|
192
221
|
break;
|
|
193
222
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// Verify that each side is different user
|
|
197
|
-
if (crossingNodes && !takerIsMaker) {
|
|
198
|
-
nodesToFill.push(crossingNodes);
|
|
223
|
+
for (const crossingNode of crossingNodes) {
|
|
224
|
+
nodesToFill.push(crossingNode);
|
|
199
225
|
}
|
|
200
226
|
}
|
|
201
227
|
return nodesToFill;
|
|
202
228
|
}
|
|
203
|
-
|
|
229
|
+
findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
|
|
230
|
+
const nodesToFill = new Array();
|
|
231
|
+
const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
|
|
232
|
+
slot, marketType, oraclePriceData);
|
|
233
|
+
const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
|
|
234
|
+
slot, marketType, oraclePriceData);
|
|
235
|
+
let nextAsk = askGenerator.next();
|
|
236
|
+
let nextBid = bidGenerator.next();
|
|
237
|
+
// check for asks that cross vBid
|
|
238
|
+
while (!nextAsk.done) {
|
|
239
|
+
const askNode = nextAsk.value;
|
|
240
|
+
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
241
|
+
if (askPrice.lte(vBid) && (0, __1.isAuctionComplete)(askNode.order, slot)) {
|
|
242
|
+
nodesToFill.push({
|
|
243
|
+
node: askNode,
|
|
244
|
+
makerNode: undefined, // filled by vAMM
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
nextAsk = askGenerator.next();
|
|
251
|
+
}
|
|
252
|
+
// check for bids that cross vAsk
|
|
253
|
+
while (!nextBid.done) {
|
|
254
|
+
const bidNode = nextBid.value;
|
|
255
|
+
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
256
|
+
if (bidPrice.gte(vAsk) && (0, __1.isAuctionComplete)(bidNode.order, slot)) {
|
|
257
|
+
nodesToFill.push({
|
|
258
|
+
node: bidNode,
|
|
259
|
+
makerNode: undefined, // filled by vAMM
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
nextBid = bidGenerator.next();
|
|
266
|
+
}
|
|
267
|
+
return nodesToFill;
|
|
268
|
+
}
|
|
269
|
+
findExpiredMarketNodesToFill(marketIndex, slot, marketType) {
|
|
204
270
|
const nodesToFill = new Array();
|
|
205
271
|
// Then see if there are orders to fill against vamm
|
|
206
272
|
for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
|
|
207
|
-
if ((0, __1.
|
|
208
|
-
(0, __1.isOrderExpired)(marketBid.order, slot)) {
|
|
273
|
+
if ((0, __1.isOrderExpired)(marketBid.order, slot)) {
|
|
209
274
|
nodesToFill.push({
|
|
210
275
|
node: marketBid,
|
|
211
276
|
});
|
|
212
277
|
}
|
|
213
278
|
}
|
|
214
279
|
for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
|
|
215
|
-
if ((0, __1.
|
|
216
|
-
(0, __1.isOrderExpired)(marketAsk.order, slot)) {
|
|
280
|
+
if ((0, __1.isOrderExpired)(marketAsk.order, slot)) {
|
|
217
281
|
nodesToFill.push({
|
|
218
282
|
node: marketAsk,
|
|
219
283
|
});
|
|
@@ -265,9 +329,12 @@ class DLOB {
|
|
|
265
329
|
nodeLists.floatingLimit.ask.getGenerator(),
|
|
266
330
|
nodeLists.market.ask.getGenerator(),
|
|
267
331
|
];
|
|
268
|
-
if (marketTypeStr === 'perp') {
|
|
332
|
+
if (marketTypeStr === 'perp' && vAsk) {
|
|
269
333
|
generatorList.push((0, NodeList_1.getVammNodeGenerator)(vAsk));
|
|
270
334
|
}
|
|
335
|
+
if (generatorList.length === 0) {
|
|
336
|
+
throw new Error('No ask generators found');
|
|
337
|
+
}
|
|
271
338
|
const askGenerators = generatorList.map((generator) => {
|
|
272
339
|
return {
|
|
273
340
|
next: generator.next(),
|
|
@@ -290,6 +357,11 @@ class DLOB {
|
|
|
290
357
|
: currentGenerator;
|
|
291
358
|
});
|
|
292
359
|
if (!bestGenerator.next.done) {
|
|
360
|
+
// skip this node if it's already completely filled
|
|
361
|
+
if (bestGenerator.next.value.isBaseFilled()) {
|
|
362
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
293
365
|
yield bestGenerator.next.value;
|
|
294
366
|
bestGenerator.next = bestGenerator.generator.next();
|
|
295
367
|
}
|
|
@@ -309,9 +381,12 @@ class DLOB {
|
|
|
309
381
|
nodeLists.floatingLimit.bid.getGenerator(),
|
|
310
382
|
nodeLists.market.bid.getGenerator(),
|
|
311
383
|
];
|
|
312
|
-
if (marketTypeStr === 'perp') {
|
|
384
|
+
if (marketTypeStr === 'perp' && vBid) {
|
|
313
385
|
generatorList.push((0, NodeList_1.getVammNodeGenerator)(vBid));
|
|
314
386
|
}
|
|
387
|
+
if (generatorList.length === 0) {
|
|
388
|
+
throw new Error('No bid generators found');
|
|
389
|
+
}
|
|
315
390
|
const bidGenerators = generatorList.map((generator) => {
|
|
316
391
|
return {
|
|
317
392
|
next: generator.next(),
|
|
@@ -334,6 +409,11 @@ class DLOB {
|
|
|
334
409
|
: currentGenerator;
|
|
335
410
|
});
|
|
336
411
|
if (!bestGenerator.next.done) {
|
|
412
|
+
// skip this node if it's already completely filled
|
|
413
|
+
if (bestGenerator.next.value.isBaseFilled()) {
|
|
414
|
+
bestGenerator.next = bestGenerator.generator.next();
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
337
417
|
yield bestGenerator.next.value;
|
|
338
418
|
bestGenerator.next = bestGenerator.generator.next();
|
|
339
419
|
}
|
|
@@ -345,54 +425,24 @@ class DLOB {
|
|
|
345
425
|
findCrossingOrders(askNode, bidNode, oraclePriceData, slot) {
|
|
346
426
|
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
347
427
|
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const containsMarketOrder = (askOrder && (0, __1.isVariant)(askOrder.orderType, 'market')) ||
|
|
351
|
-
(bidOrder && (0, __1.isVariant)(bidOrder.orderType, 'market'));
|
|
352
|
-
if (!containsMarketOrder || bidPrice.lt(askPrice)) {
|
|
353
|
-
// no market orders, and no crossing orders - return
|
|
354
|
-
if (askNode.isVammNode() && !bidNode.isVammNode()) {
|
|
355
|
-
return {
|
|
356
|
-
exhaustedSide: 'bid',
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
else if (!askNode.isVammNode() && bidNode.isVammNode()) {
|
|
360
|
-
return {
|
|
361
|
-
exhaustedSide: 'ask',
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
return {
|
|
366
|
-
exhaustedSide: 'both',
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
// User bid crosses the vamm ask
|
|
371
|
-
if (askNode.isVammNode()) {
|
|
372
|
-
if (!(0, __1.isAuctionComplete)(bidOrder, slot)) {
|
|
373
|
-
return {
|
|
374
|
-
exhaustedSide: 'bid',
|
|
375
|
-
};
|
|
376
|
-
}
|
|
428
|
+
// orders don't cross - we're done walkin gup the book
|
|
429
|
+
if (bidPrice.lt(askPrice)) {
|
|
377
430
|
return {
|
|
378
|
-
crossingNodes:
|
|
379
|
-
|
|
380
|
-
},
|
|
381
|
-
exhaustedSide: 'bid',
|
|
431
|
+
crossingNodes: [],
|
|
432
|
+
exhaustedSide: 'nocross',
|
|
382
433
|
};
|
|
383
434
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
435
|
+
const bidOrder = bidNode.order;
|
|
436
|
+
const askOrder = askNode.order;
|
|
437
|
+
// Can't match two maker orders or if maker and taker are the same
|
|
438
|
+
const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
|
|
439
|
+
if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
|
|
440
|
+
// don't have a principle way to pick which one to exhaust,
|
|
441
|
+
// exhaust each one 50% of the time so we can try each one against other orders
|
|
442
|
+
const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
|
|
391
443
|
return {
|
|
392
|
-
crossingNodes:
|
|
393
|
-
|
|
394
|
-
},
|
|
395
|
-
exhaustedSide: 'ask',
|
|
444
|
+
crossingNodes: [],
|
|
445
|
+
exhaustedSide,
|
|
396
446
|
};
|
|
397
447
|
}
|
|
398
448
|
const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
|
|
@@ -407,44 +457,63 @@ class DLOB {
|
|
|
407
457
|
else {
|
|
408
458
|
exhaustedSide = 'bid';
|
|
409
459
|
}
|
|
410
|
-
//
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
// update the orders as if they fill - so we don't try to match them on the next iteration
|
|
460
|
+
// update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
|
|
461
|
+
// NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
|
|
462
|
+
// then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
|
|
463
|
+
// a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
|
|
464
|
+
// than updating the existing DLOB based on events.
|
|
417
465
|
if (exhaustedSide === 'ask') {
|
|
418
|
-
|
|
466
|
+
// bid partially filled
|
|
467
|
+
const newBidOrder = { ...bidOrder };
|
|
468
|
+
newBidOrder.baseAssetAmountFilled =
|
|
419
469
|
bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
|
|
420
|
-
|
|
470
|
+
this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
|
|
471
|
+
// ask completely filled
|
|
472
|
+
const newAskOrder = { ...askOrder };
|
|
473
|
+
newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
|
|
474
|
+
this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
|
|
421
475
|
}
|
|
422
476
|
else if (exhaustedSide === 'bid') {
|
|
423
|
-
|
|
477
|
+
// ask partially filled
|
|
478
|
+
const newAskOrder = { ...askOrder };
|
|
479
|
+
newAskOrder.baseAssetAmountFilled =
|
|
424
480
|
askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
|
|
425
|
-
|
|
481
|
+
this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
|
|
482
|
+
// bid completely filled
|
|
483
|
+
const newBidOrder = { ...bidOrder };
|
|
484
|
+
newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
|
|
485
|
+
this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
|
|
426
486
|
}
|
|
427
487
|
else {
|
|
428
|
-
|
|
429
|
-
|
|
488
|
+
// both completely filled
|
|
489
|
+
const newBidOrder = { ...bidOrder };
|
|
490
|
+
newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
|
|
491
|
+
this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
|
|
492
|
+
const newAskOrder = { ...askOrder };
|
|
493
|
+
newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
|
|
494
|
+
this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
|
|
430
495
|
}
|
|
431
496
|
// Bid is maker
|
|
432
497
|
if (bidOrder.postOnly) {
|
|
433
498
|
return {
|
|
434
|
-
crossingNodes:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
499
|
+
crossingNodes: [
|
|
500
|
+
{
|
|
501
|
+
node: askNode,
|
|
502
|
+
makerNode: bidNode,
|
|
503
|
+
},
|
|
504
|
+
],
|
|
438
505
|
exhaustedSide,
|
|
439
506
|
};
|
|
440
507
|
}
|
|
441
508
|
// Ask is maker
|
|
442
509
|
if (askOrder.postOnly) {
|
|
443
510
|
return {
|
|
444
|
-
crossingNodes:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
511
|
+
crossingNodes: [
|
|
512
|
+
{
|
|
513
|
+
node: bidNode,
|
|
514
|
+
makerNode: askNode,
|
|
515
|
+
},
|
|
516
|
+
],
|
|
448
517
|
exhaustedSide,
|
|
449
518
|
};
|
|
450
519
|
}
|
|
@@ -454,10 +523,12 @@ class DLOB {
|
|
|
454
523
|
? [askNode, bidNode]
|
|
455
524
|
: [bidNode, askNode];
|
|
456
525
|
return {
|
|
457
|
-
crossingNodes:
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
526
|
+
crossingNodes: [
|
|
527
|
+
{
|
|
528
|
+
node: newerNode,
|
|
529
|
+
makerNode: olderNode,
|
|
530
|
+
},
|
|
531
|
+
],
|
|
461
532
|
exhaustedSide,
|
|
462
533
|
};
|
|
463
534
|
}
|
package/lib/dlob/DLOBNode.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface DLOBNode {
|
|
|
5
5
|
getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
|
|
6
6
|
isVammNode(): boolean;
|
|
7
7
|
order: Order | undefined;
|
|
8
|
+
isBaseFilled(): boolean;
|
|
8
9
|
haveFilled: boolean;
|
|
9
10
|
userAccount: PublicKey | undefined;
|
|
10
11
|
market: SpotMarketAccount | PerpMarketAccount;
|
|
@@ -20,6 +21,7 @@ export declare abstract class OrderNode implements DLOBNode {
|
|
|
20
21
|
abstract getSortValue(order: Order): BN;
|
|
21
22
|
getLabel(): string;
|
|
22
23
|
getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
|
|
24
|
+
isBaseFilled(): boolean;
|
|
23
25
|
isVammNode(): boolean;
|
|
24
26
|
}
|
|
25
27
|
export declare class LimitOrderNode extends OrderNode {
|
package/lib/dlob/DLOBNode.js
CHANGED
package/lib/dlob/NodeList.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { BN, MarketTypeStr, Order, PerpMarketAccount, SpotMarketAccount } from '
|
|
|
3
3
|
import { PublicKey } from '@solana/web3.js';
|
|
4
4
|
import { DLOBNode, DLOBNodeMap } from './DLOBNode';
|
|
5
5
|
export declare type SortDirection = 'asc' | 'desc';
|
|
6
|
-
export declare function getOrderSignature(orderId:
|
|
6
|
+
export declare function getOrderSignature(orderId: number, userAccount: PublicKey): string;
|
|
7
7
|
export interface DLOBNodeGenerator {
|
|
8
8
|
getGenerator(): Generator<DLOBNode>;
|
|
9
9
|
}
|
|
@@ -14,6 +14,7 @@ export declare class NodeList<NodeType extends keyof DLOBNodeMap> implements DLO
|
|
|
14
14
|
length: number;
|
|
15
15
|
nodeMap: Map<string, DLOBNodeMap[NodeType]>;
|
|
16
16
|
constructor(nodeType: NodeType, sortDirection: SortDirection);
|
|
17
|
+
clear(): void;
|
|
17
18
|
insert(order: Order, marketType: MarketTypeStr, market: PerpMarketAccount | SpotMarketAccount, userAccount: PublicKey): void;
|
|
18
19
|
prependNode(currentNode: DLOBNodeMap[NodeType], newNode: DLOBNodeMap[NodeType]): boolean;
|
|
19
20
|
update(order: Order, userAccount: PublicKey): void;
|
package/lib/dlob/NodeList.js
CHANGED
|
@@ -14,6 +14,11 @@ class NodeList {
|
|
|
14
14
|
this.length = 0;
|
|
15
15
|
this.nodeMap = new Map();
|
|
16
16
|
}
|
|
17
|
+
clear() {
|
|
18
|
+
this.head = undefined;
|
|
19
|
+
this.length = 0;
|
|
20
|
+
this.nodeMap.clear();
|
|
21
|
+
}
|
|
17
22
|
insert(order, marketType, market, userAccount) {
|
|
18
23
|
if ((0, __1.isVariant)(order.status, 'init')) {
|
|
19
24
|
return;
|
|
@@ -25,11 +30,11 @@ class NodeList {
|
|
|
25
30
|
market = market;
|
|
26
31
|
}
|
|
27
32
|
const newNode = (0, DLOBNode_1.createNode)(this.nodeType, order, market, userAccount);
|
|
28
|
-
const
|
|
29
|
-
if (this.nodeMap.has(
|
|
33
|
+
const orderSignature = getOrderSignature(order.orderId, userAccount);
|
|
34
|
+
if (this.nodeMap.has(orderSignature)) {
|
|
30
35
|
return;
|
|
31
36
|
}
|
|
32
|
-
this.nodeMap.set(
|
|
37
|
+
this.nodeMap.set(orderSignature, newNode);
|
|
33
38
|
this.length += 1;
|
|
34
39
|
if (this.head === undefined) {
|
|
35
40
|
this.head = newNode;
|
|
@@ -86,7 +91,7 @@ class NodeList {
|
|
|
86
91
|
if (node.previous) {
|
|
87
92
|
node.previous.next = node.next;
|
|
88
93
|
}
|
|
89
|
-
if (this.head && node.order.orderId
|
|
94
|
+
if (this.head && node.order.orderId === this.head.order.orderId) {
|
|
90
95
|
this.head = node.next;
|
|
91
96
|
}
|
|
92
97
|
node.previous = undefined;
|
|
@@ -132,6 +137,7 @@ function* getVammNodeGenerator(price) {
|
|
|
132
137
|
order: undefined,
|
|
133
138
|
market: undefined,
|
|
134
139
|
userAccount: undefined,
|
|
140
|
+
isBaseFilled: () => false,
|
|
135
141
|
haveFilled: false,
|
|
136
142
|
};
|
|
137
143
|
}
|