@drift-labs/sdk 2.0.20 → 2.1.1

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.
@@ -1,8 +1,8 @@
1
1
  /// <reference types="bn.js" />
2
2
  import { NodeList } from './NodeList';
3
- import { MarketType, BN, DriftClient, Order, SpotMarketAccount, PerpMarketAccount, OraclePriceData, SlotSubscriber, MarketTypeStr, StateAccount, UserMap } from '..';
3
+ import { MarketType, BN, DriftClient, Order, SpotMarketAccount, PerpMarketAccount, OraclePriceData, SlotSubscriber, MarketTypeStr, StateAccount, UserMap, OrderRecord, OrderActionRecord } from '..';
4
4
  import { PublicKey } from '@solana/web3.js';
5
- import { DLOBNode, TriggerOrderNode } from '..';
5
+ import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
6
6
  import { DLOBOrders } from './DLOBOrders';
7
7
  export declare type MarketNodeLists = {
8
8
  limit: {
@@ -43,10 +43,15 @@ export declare class DLOB {
43
43
  */
44
44
  initFromUserMap(userMap: UserMap): Promise<boolean>;
45
45
  initFromOrders(dlobOrders: DLOBOrders): boolean;
46
+ handleOrderRecord(record: OrderRecord): void;
47
+ handleOrderActionRecord(record: OrderActionRecord): void;
46
48
  insertOrder(order: Order, userAccount: PublicKey, onInsert?: OrderBookCallback): void;
47
49
  addOrderList(marketType: MarketTypeStr, marketIndex: number): void;
50
+ updateOrder(order: Order, userAccount: PublicKey, cumulativeBaseAssetAmountFilled: BN, onUpdate?: OrderBookCallback): void;
48
51
  trigger(order: Order, userAccount: PublicKey, onTrigger?: OrderBookCallback): void;
52
+ delete(order: Order, userAccount: PublicKey, onDelete?: OrderBookCallback): void;
49
53
  getListForOrder(order: Order): NodeList<any> | undefined;
54
+ getOrder(orderId: number, userAccount: PublicKey): Order | undefined;
50
55
  findNodesToFill(marketIndex: number, fallbackBid: BN | undefined, fallbackAsk: BN | undefined, slot: number, ts: number, marketType: MarketType, oraclePriceData: OraclePriceData, stateAccount: StateAccount, marketAccount: PerpMarketAccount | SpotMarketAccount): NodeToFill[];
51
56
  findLimitOrderNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid: BN | undefined): NodeToFill[];
52
57
  findMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid?: BN | undefined): NodeToFill[];
@@ -71,6 +76,6 @@ export declare class DLOB {
71
76
  findNodesToTrigger(marketIndex: number, slot: number, oraclePrice: BN, marketType: MarketType, stateAccount: StateAccount): NodeToTrigger[];
72
77
  printTopOfOrderLists(sdkConfig: any, driftClient: DriftClient, slotSubscriber: SlotSubscriber, marketIndex: number, marketType: MarketType): void;
73
78
  getDLOBOrders(): DLOBOrders;
74
- getOrdersForMarket(marketIndex: number, marketType: MarketType, nodeLists: MarketNodeLists): DLOBOrders;
79
+ getNodeLists(): Generator<NodeList<DLOBNodeType>>;
75
80
  }
76
81
  export {};
package/lib/dlob/DLOB.js CHANGED
@@ -63,6 +63,56 @@ class DLOB {
63
63
  this.initialized = true;
64
64
  return true;
65
65
  }
66
+ handleOrderRecord(record) {
67
+ this.insertOrder(record.order, record.user);
68
+ }
69
+ handleOrderActionRecord(record) {
70
+ if ((0, __1.isOneOfVariant)(record.action, ['place', 'expire'])) {
71
+ return;
72
+ }
73
+ if ((0, __1.isVariant)(record.action, 'trigger')) {
74
+ if (record.taker !== null) {
75
+ const takerOrder = this.getOrder(record.takerOrderId, record.taker);
76
+ if (takerOrder) {
77
+ this.trigger(takerOrder, record.taker);
78
+ }
79
+ }
80
+ if (record.maker !== null) {
81
+ const makerOrder = this.getOrder(record.makerOrderId, record.maker);
82
+ if (makerOrder) {
83
+ this.trigger(makerOrder, record.maker);
84
+ }
85
+ }
86
+ }
87
+ else if ((0, __1.isVariant)(record.action, 'fill')) {
88
+ if (record.taker !== null) {
89
+ const takerOrder = this.getOrder(record.takerOrderId, record.taker);
90
+ if (takerOrder) {
91
+ this.updateOrder(takerOrder, record.taker, record.takerOrderCumulativeBaseAssetAmountFilled);
92
+ }
93
+ }
94
+ if (record.maker !== null) {
95
+ const makerOrder = this.getOrder(record.makerOrderId, record.maker);
96
+ if (makerOrder) {
97
+ this.updateOrder(makerOrder, record.maker, record.makerOrderCumulativeBaseAssetAmountFilled);
98
+ }
99
+ }
100
+ }
101
+ else if ((0, __1.isVariant)(record.action, 'cancel')) {
102
+ if (record.taker !== null) {
103
+ const takerOrder = this.getOrder(record.takerOrderId, record.taker);
104
+ if (takerOrder) {
105
+ this.delete(takerOrder, record.taker);
106
+ }
107
+ }
108
+ if (record.maker !== null) {
109
+ const makerOrder = this.getOrder(record.makerOrderId, record.maker);
110
+ if (makerOrder) {
111
+ this.delete(makerOrder, record.maker);
112
+ }
113
+ }
114
+ }
115
+ }
66
116
  insertOrder(order, userAccount, onInsert) {
67
117
  var _a;
68
118
  if ((0, __1.isVariant)(order.status, 'init')) {
@@ -102,11 +152,29 @@ class DLOB {
102
152
  },
103
153
  });
104
154
  }
155
+ updateOrder(order, userAccount, cumulativeBaseAssetAmountFilled, onUpdate) {
156
+ var _a;
157
+ if (order.baseAssetAmount.eq(cumulativeBaseAssetAmountFilled)) {
158
+ this.delete(order, userAccount);
159
+ return;
160
+ }
161
+ if (order.baseAssetAmountFilled.eq(cumulativeBaseAssetAmountFilled)) {
162
+ return;
163
+ }
164
+ order.baseAssetAmountFilled = cumulativeBaseAssetAmountFilled;
165
+ (_a = this.getListForOrder(order)) === null || _a === void 0 ? void 0 : _a.update(order, userAccount);
166
+ if (onUpdate) {
167
+ onUpdate();
168
+ }
169
+ }
105
170
  trigger(order, userAccount, onTrigger) {
106
171
  var _a;
107
172
  if ((0, __1.isVariant)(order.status, 'init')) {
108
173
  return;
109
174
  }
175
+ if ((0, __1.isTriggered)(order)) {
176
+ return;
177
+ }
110
178
  const marketType = (0, __1.getVariant)(order.marketType);
111
179
  const triggerList = this.orderLists.get(marketType).get(order.marketIndex)
112
180
  .trigger[(0, __1.isVariant)(order.triggerCondition, 'above') ? 'above' : 'below'];
@@ -116,6 +184,16 @@ class DLOB {
116
184
  onTrigger();
117
185
  }
118
186
  }
187
+ delete(order, userAccount, onDelete) {
188
+ var _a;
189
+ if ((0, __1.isVariant)(order.status, 'init')) {
190
+ return;
191
+ }
192
+ (_a = this.getListForOrder(order)) === null || _a === void 0 ? void 0 : _a.remove(order, userAccount);
193
+ if (onDelete) {
194
+ onDelete();
195
+ }
196
+ }
119
197
  getListForOrder(order) {
120
198
  const isInactiveTriggerOrder = (0, __1.mustBeTriggered)(order) && !(0, __1.isTriggered)(order);
121
199
  let type;
@@ -144,6 +222,15 @@ class DLOB {
144
222
  }
145
223
  return this.orderLists.get(marketType).get(order.marketIndex)[type][subType];
146
224
  }
225
+ getOrder(orderId, userAccount) {
226
+ for (const nodeList of this.getNodeLists()) {
227
+ const node = nodeList.get(orderId, userAccount);
228
+ if (node) {
229
+ return node.order;
230
+ }
231
+ }
232
+ return undefined;
233
+ }
147
234
  findNodesToFill(marketIndex, fallbackBid, fallbackAsk, slot, ts, marketType, oraclePriceData, stateAccount, marketAccount) {
148
235
  if ((0, exchangeStatus_1.fillPaused)(stateAccount, marketAccount)) {
149
236
  return [];
@@ -644,66 +731,38 @@ class DLOB {
644
731
  }
645
732
  }
646
733
  getDLOBOrders() {
647
- let dlobOrders = [];
648
- for (const [marketIndex, nodeLists] of this.orderLists.get('perp')) {
649
- dlobOrders = dlobOrders.concat(this.getOrdersForMarket(marketIndex, __1.MarketType.PERP, nodeLists));
650
- }
651
- for (const [marketIndex, nodeLists] of this.orderLists.get('spot')) {
652
- dlobOrders = dlobOrders.concat(this.getOrdersForMarket(marketIndex, __1.MarketType.SPOT, nodeLists));
734
+ const dlobOrders = [];
735
+ for (const nodeList of this.getNodeLists()) {
736
+ for (const node of nodeList.getGenerator()) {
737
+ dlobOrders.push({
738
+ user: node.userAccount,
739
+ order: node.order,
740
+ });
741
+ }
653
742
  }
654
743
  return dlobOrders;
655
744
  }
656
- getOrdersForMarket(marketIndex, marketType, nodeLists) {
657
- const orders = [];
658
- for (const node of nodeLists.limit.bid.getGenerator()) {
659
- orders.push({
660
- user: node.userAccount,
661
- order: node.order,
662
- });
663
- }
664
- for (const node of nodeLists.limit.ask.getGenerator()) {
665
- orders.push({
666
- user: node.userAccount,
667
- order: node.order,
668
- });
669
- }
670
- for (const node of nodeLists.market.bid.getGenerator()) {
671
- orders.push({
672
- user: node.userAccount,
673
- order: node.order,
674
- });
675
- }
676
- for (const node of nodeLists.market.ask.getGenerator()) {
677
- orders.push({
678
- user: node.userAccount,
679
- order: node.order,
680
- });
681
- }
682
- for (const node of nodeLists.floatingLimit.bid.getGenerator()) {
683
- orders.push({
684
- user: node.userAccount,
685
- order: node.order,
686
- });
687
- }
688
- for (const node of nodeLists.floatingLimit.ask.getGenerator()) {
689
- orders.push({
690
- user: node.userAccount,
691
- order: node.order,
692
- });
693
- }
694
- for (const node of nodeLists.trigger.below.getGenerator()) {
695
- orders.push({
696
- user: node.userAccount,
697
- order: node.order,
698
- });
699
- }
700
- for (const node of nodeLists.trigger.above.getGenerator()) {
701
- orders.push({
702
- user: node.userAccount,
703
- order: node.order,
704
- });
745
+ *getNodeLists() {
746
+ for (const [_, nodeLists] of this.orderLists.get('perp')) {
747
+ yield nodeLists.limit.bid;
748
+ yield nodeLists.limit.ask;
749
+ yield nodeLists.market.bid;
750
+ yield nodeLists.market.ask;
751
+ yield nodeLists.floatingLimit.bid;
752
+ yield nodeLists.floatingLimit.ask;
753
+ yield nodeLists.trigger.above;
754
+ yield nodeLists.trigger.below;
755
+ }
756
+ for (const [_, nodeLists] of this.orderLists.get('spot')) {
757
+ yield nodeLists.limit.bid;
758
+ yield nodeLists.limit.ask;
759
+ yield nodeLists.market.bid;
760
+ yield nodeLists.market.ask;
761
+ yield nodeLists.floatingLimit.bid;
762
+ yield nodeLists.floatingLimit.ask;
763
+ yield nodeLists.trigger.above;
764
+ yield nodeLists.trigger.below;
705
765
  }
706
- return orders;
707
766
  }
708
767
  }
709
768
  exports.DLOB = DLOB;
@@ -21,6 +21,7 @@ export declare class NodeList<NodeType extends keyof DLOBNodeMap> implements DLO
21
21
  remove(order: Order, userAccount: PublicKey): void;
22
22
  getGenerator(): Generator<DLOBNode>;
23
23
  has(order: Order, userAccount: PublicKey): boolean;
24
+ get(orderId: number, userAccount: PublicKey): DLOBNodeMap[NodeType] | undefined;
24
25
  print(): void;
25
26
  printTop(): void;
26
27
  }
@@ -104,6 +104,9 @@ class NodeList {
104
104
  has(order, userAccount) {
105
105
  return this.nodeMap.has(getOrderSignature(order.orderId, userAccount));
106
106
  }
107
+ get(orderId, userAccount) {
108
+ return this.nodeMap.get(getOrderSignature(orderId, userAccount));
109
+ }
107
110
  print() {
108
111
  let currentNode = this.head;
109
112
  while (currentNode !== undefined) {
package/lib/math/amm.js CHANGED
@@ -213,7 +213,7 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
213
213
  }
214
214
  exports.calculateInventoryScale = calculateInventoryScale;
215
215
  function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, netBaseAssetAmount, reservePrice, totalFeeMinusDistributions) {
216
- // inventory skew
216
+ // vAMM skew
217
217
  const netBaseAssetValue = quoteAssetReserve
218
218
  .sub(terminalQuoteAssetReserve)
219
219
  .mul(pegMultiplier)
@@ -221,8 +221,8 @@ function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuote
221
221
  const localBaseAssetValue = netBaseAssetAmount
222
222
  .mul(reservePrice)
223
223
  .div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO.mul(numericConstants_1.PRICE_PRECISION));
224
- const effectiveLeverage = localBaseAssetValue.sub(netBaseAssetValue).toNumber() /
225
- (Math.max(0, totalFeeMinusDistributions.toNumber()) + 1) +
224
+ const effectiveGap = Math.max(0, localBaseAssetValue.sub(netBaseAssetValue).toNumber());
225
+ const effectiveLeverage = effectiveGap / (Math.max(0, totalFeeMinusDistributions.toNumber()) + 1) +
226
226
  1 / numericConstants_1.QUOTE_PRECISION.toNumber();
227
227
  return effectiveLeverage;
228
228
  }
@@ -309,7 +309,7 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
309
309
  spreadTerms.effectiveLeverageCapped = spreadScale;
310
310
  if (baseAssetAmountWithAmm.gt(numericConstants_1.ZERO)) {
311
311
  longSpread *= spreadScale;
312
- shortSpread = Math.floor(longSpread);
312
+ longSpread = Math.floor(longSpread);
313
313
  }
314
314
  else {
315
315
  shortSpread *= spreadScale;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.0.20",
3
+ "version": "2.1.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assert = void 0;
4
+ function assert(condition, error) {
5
+ if (!condition) {
6
+ throw new Error(error || 'Unspecified AssertionError');
7
+ }
8
+ }
9
+ exports.assert = assert;
package/src/dlob/DLOB.ts CHANGED
@@ -24,6 +24,8 @@ import {
24
24
  isTriggered,
25
25
  getLimitPrice,
26
26
  UserMap,
27
+ OrderRecord,
28
+ OrderActionRecord,
27
29
  } from '..';
28
30
  import { PublicKey } from '@solana/web3.js';
29
31
  import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
@@ -131,6 +133,68 @@ export class DLOB {
131
133
  return true;
132
134
  }
133
135
 
136
+ public handleOrderRecord(record: OrderRecord): void {
137
+ this.insertOrder(record.order, record.user);
138
+ }
139
+
140
+ public handleOrderActionRecord(record: OrderActionRecord): void {
141
+ if (isOneOfVariant(record.action, ['place', 'expire'])) {
142
+ return;
143
+ }
144
+
145
+ if (isVariant(record.action, 'trigger')) {
146
+ if (record.taker !== null) {
147
+ const takerOrder = this.getOrder(record.takerOrderId, record.taker);
148
+ if (takerOrder) {
149
+ this.trigger(takerOrder, record.taker);
150
+ }
151
+ }
152
+
153
+ if (record.maker !== null) {
154
+ const makerOrder = this.getOrder(record.makerOrderId, record.maker);
155
+ if (makerOrder) {
156
+ this.trigger(makerOrder, record.maker);
157
+ }
158
+ }
159
+ } else if (isVariant(record.action, 'fill')) {
160
+ if (record.taker !== null) {
161
+ const takerOrder = this.getOrder(record.takerOrderId, record.taker);
162
+ if (takerOrder) {
163
+ this.updateOrder(
164
+ takerOrder,
165
+ record.taker,
166
+ record.takerOrderCumulativeBaseAssetAmountFilled
167
+ );
168
+ }
169
+ }
170
+
171
+ if (record.maker !== null) {
172
+ const makerOrder = this.getOrder(record.makerOrderId, record.maker);
173
+ if (makerOrder) {
174
+ this.updateOrder(
175
+ makerOrder,
176
+ record.maker,
177
+ record.makerOrderCumulativeBaseAssetAmountFilled
178
+ );
179
+ }
180
+ }
181
+ } else if (isVariant(record.action, 'cancel')) {
182
+ if (record.taker !== null) {
183
+ const takerOrder = this.getOrder(record.takerOrderId, record.taker);
184
+ if (takerOrder) {
185
+ this.delete(takerOrder, record.taker);
186
+ }
187
+ }
188
+
189
+ if (record.maker !== null) {
190
+ const makerOrder = this.getOrder(record.makerOrderId, record.maker);
191
+ if (makerOrder) {
192
+ this.delete(makerOrder, record.maker);
193
+ }
194
+ }
195
+ }
196
+ }
197
+
134
198
  public insertOrder(
135
199
  order: Order,
136
200
  userAccount: PublicKey,
@@ -179,6 +243,29 @@ export class DLOB {
179
243
  });
180
244
  }
181
245
 
246
+ public updateOrder(
247
+ order: Order,
248
+ userAccount: PublicKey,
249
+ cumulativeBaseAssetAmountFilled: BN,
250
+ onUpdate?: OrderBookCallback
251
+ ): void {
252
+ if (order.baseAssetAmount.eq(cumulativeBaseAssetAmountFilled)) {
253
+ this.delete(order, userAccount);
254
+ return;
255
+ }
256
+
257
+ if (order.baseAssetAmountFilled.eq(cumulativeBaseAssetAmountFilled)) {
258
+ return;
259
+ }
260
+
261
+ order.baseAssetAmountFilled = cumulativeBaseAssetAmountFilled;
262
+ this.getListForOrder(order)?.update(order, userAccount);
263
+
264
+ if (onUpdate) {
265
+ onUpdate();
266
+ }
267
+ }
268
+
182
269
  public trigger(
183
270
  order: Order,
184
271
  userAccount: PublicKey,
@@ -188,6 +275,10 @@ export class DLOB {
188
275
  return;
189
276
  }
190
277
 
278
+ if (isTriggered(order)) {
279
+ return;
280
+ }
281
+
191
282
  const marketType = getVariant(order.marketType) as MarketTypeStr;
192
283
 
193
284
  const triggerList = this.orderLists.get(marketType).get(order.marketIndex)
@@ -200,6 +291,21 @@ export class DLOB {
200
291
  }
201
292
  }
202
293
 
294
+ public delete(
295
+ order: Order,
296
+ userAccount: PublicKey,
297
+ onDelete?: OrderBookCallback
298
+ ): void {
299
+ if (isVariant(order.status, 'init')) {
300
+ return;
301
+ }
302
+
303
+ this.getListForOrder(order)?.remove(order, userAccount);
304
+ if (onDelete) {
305
+ onDelete();
306
+ }
307
+ }
308
+
203
309
  public getListForOrder(order: Order): NodeList<any> | undefined {
204
310
  const isInactiveTriggerOrder =
205
311
  mustBeTriggered(order) && !isTriggered(order);
@@ -233,6 +339,17 @@ export class DLOB {
233
339
  ];
234
340
  }
235
341
 
342
+ public getOrder(orderId: number, userAccount: PublicKey): Order | undefined {
343
+ for (const nodeList of this.getNodeLists()) {
344
+ const node = nodeList.get(orderId, userAccount);
345
+ if (node) {
346
+ return node.order;
347
+ }
348
+ }
349
+
350
+ return undefined;
351
+ }
352
+
236
353
  public findNodesToFill(
237
354
  marketIndex: number,
238
355
  fallbackBid: BN | undefined,
@@ -1164,86 +1281,41 @@ export class DLOB {
1164
1281
  }
1165
1282
 
1166
1283
  public getDLOBOrders(): DLOBOrders {
1167
- let dlobOrders: DLOBOrders = [];
1168
-
1169
- for (const [marketIndex, nodeLists] of this.orderLists.get('perp')) {
1170
- dlobOrders = dlobOrders.concat(
1171
- this.getOrdersForMarket(marketIndex, MarketType.PERP, nodeLists)
1172
- );
1173
- }
1284
+ const dlobOrders: DLOBOrders = [];
1174
1285
 
1175
- for (const [marketIndex, nodeLists] of this.orderLists.get('spot')) {
1176
- dlobOrders = dlobOrders.concat(
1177
- this.getOrdersForMarket(marketIndex, MarketType.SPOT, nodeLists)
1178
- );
1286
+ for (const nodeList of this.getNodeLists()) {
1287
+ for (const node of nodeList.getGenerator()) {
1288
+ dlobOrders.push({
1289
+ user: node.userAccount,
1290
+ order: node.order,
1291
+ });
1292
+ }
1179
1293
  }
1180
1294
 
1181
1295
  return dlobOrders;
1182
1296
  }
1183
1297
 
1184
- getOrdersForMarket(
1185
- marketIndex: number,
1186
- marketType: MarketType,
1187
- nodeLists: MarketNodeLists
1188
- ): DLOBOrders {
1189
- const orders: { user: PublicKey; order: Order }[] = [];
1190
-
1191
- for (const node of nodeLists.limit.bid.getGenerator()) {
1192
- orders.push({
1193
- user: node.userAccount,
1194
- order: node.order,
1195
- });
1196
- }
1197
-
1198
- for (const node of nodeLists.limit.ask.getGenerator()) {
1199
- orders.push({
1200
- user: node.userAccount,
1201
- order: node.order,
1202
- });
1203
- }
1204
-
1205
- for (const node of nodeLists.market.bid.getGenerator()) {
1206
- orders.push({
1207
- user: node.userAccount,
1208
- order: node.order,
1209
- });
1298
+ *getNodeLists(): Generator<NodeList<DLOBNodeType>> {
1299
+ for (const [_, nodeLists] of this.orderLists.get('perp')) {
1300
+ yield nodeLists.limit.bid;
1301
+ yield nodeLists.limit.ask;
1302
+ yield nodeLists.market.bid;
1303
+ yield nodeLists.market.ask;
1304
+ yield nodeLists.floatingLimit.bid;
1305
+ yield nodeLists.floatingLimit.ask;
1306
+ yield nodeLists.trigger.above;
1307
+ yield nodeLists.trigger.below;
1210
1308
  }
1211
1309
 
1212
- for (const node of nodeLists.market.ask.getGenerator()) {
1213
- orders.push({
1214
- user: node.userAccount,
1215
- order: node.order,
1216
- });
1310
+ for (const [_, nodeLists] of this.orderLists.get('spot')) {
1311
+ yield nodeLists.limit.bid;
1312
+ yield nodeLists.limit.ask;
1313
+ yield nodeLists.market.bid;
1314
+ yield nodeLists.market.ask;
1315
+ yield nodeLists.floatingLimit.bid;
1316
+ yield nodeLists.floatingLimit.ask;
1317
+ yield nodeLists.trigger.above;
1318
+ yield nodeLists.trigger.below;
1217
1319
  }
1218
-
1219
- for (const node of nodeLists.floatingLimit.bid.getGenerator()) {
1220
- orders.push({
1221
- user: node.userAccount,
1222
- order: node.order,
1223
- });
1224
- }
1225
-
1226
- for (const node of nodeLists.floatingLimit.ask.getGenerator()) {
1227
- orders.push({
1228
- user: node.userAccount,
1229
- order: node.order,
1230
- });
1231
- }
1232
-
1233
- for (const node of nodeLists.trigger.below.getGenerator()) {
1234
- orders.push({
1235
- user: node.userAccount,
1236
- order: node.order,
1237
- });
1238
- }
1239
-
1240
- for (const node of nodeLists.trigger.above.getGenerator()) {
1241
- orders.push({
1242
- user: node.userAccount,
1243
- order: node.order,
1244
- });
1245
- }
1246
-
1247
- return orders;
1248
1320
  }
1249
1321
  }
@@ -146,6 +146,13 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
146
146
  return this.nodeMap.has(getOrderSignature(order.orderId, userAccount));
147
147
  }
148
148
 
149
+ public get(
150
+ orderId: number,
151
+ userAccount: PublicKey
152
+ ): DLOBNodeMap[NodeType] | undefined {
153
+ return this.nodeMap.get(getOrderSignature(orderId, userAccount));
154
+ }
155
+
149
156
  public print(): void {
150
157
  let currentNode = this.head;
151
158
  while (currentNode !== undefined) {
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+ var __awaiter =
3
+ (this && this.__awaiter) ||
4
+ function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) {
6
+ return value instanceof P
7
+ ? value
8
+ : new P(function (resolve) {
9
+ resolve(value);
10
+ });
11
+ }
12
+ return new (P || (P = Promise))(function (resolve, reject) {
13
+ function fulfilled(value) {
14
+ try {
15
+ step(generator.next(value));
16
+ } catch (e) {
17
+ reject(e);
18
+ }
19
+ }
20
+ function rejected(value) {
21
+ try {
22
+ step(generator['throw'](value));
23
+ } catch (e) {
24
+ reject(e);
25
+ }
26
+ }
27
+ function step(result) {
28
+ result.done
29
+ ? resolve(result.value)
30
+ : adopt(result.value).then(fulfilled, rejected);
31
+ }
32
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
33
+ });
34
+ };
35
+ Object.defineProperty(exports, '__esModule', { value: true });
36
+ exports.getTokenAddress = void 0;
37
+ const anchor_1 = require('@project-serum/anchor');
38
+ const __1 = require('..');
39
+ const spl_token_1 = require('@solana/spl-token');
40
+ const web3_js_1 = require('@solana/web3.js');
41
+ const __2 = require('..');
42
+ const banks_1 = require('../constants/spotMarkets');
43
+ const getTokenAddress = (mintAddress, userPubKey) => {
44
+ return spl_token_1.Token.getAssociatedTokenAddress(
45
+ new web3_js_1.PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`),
46
+ spl_token_1.TOKEN_PROGRAM_ID,
47
+ new web3_js_1.PublicKey(mintAddress),
48
+ new web3_js_1.PublicKey(userPubKey)
49
+ );
50
+ };
51
+ exports.getTokenAddress = getTokenAddress;
52
+ const main = () =>
53
+ __awaiter(void 0, void 0, void 0, function* () {
54
+ // Initialize Drift SDK
55
+ const sdkConfig = __2.initialize({ env: 'devnet' });
56
+ // Set up the Wallet and Provider
57
+ const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
58
+ const keypair = web3_js_1.Keypair.fromSecretKey(
59
+ Uint8Array.from(JSON.parse(privateKey))
60
+ );
61
+ const wallet = new __1.Wallet(keypair);
62
+ // Set up the Connection
63
+ const rpcAddress = process.env.RPC_ADDRESS; // can use: https://api.devnet.solana.com for devnet; https://api.mainnet-beta.solana.com for mainnet;
64
+ const connection = new web3_js_1.Connection(rpcAddress);
65
+ // Set up the Provider
66
+ const provider = new anchor_1.AnchorProvider(
67
+ connection,
68
+ wallet,
69
+ anchor_1.AnchorProvider.defaultOptions()
70
+ );
71
+ // Check SOL Balance
72
+ const lamportsBalance = yield connection.getBalance(wallet.publicKey);
73
+ console.log('SOL balance:', lamportsBalance / Math.pow(10, 9));
74
+ // Misc. other things to set up
75
+ const usdcTokenAddress = yield exports.getTokenAddress(
76
+ sdkConfig.USDC_MINT_ADDRESS,
77
+ wallet.publicKey.toString()
78
+ );
79
+ // Set up the Drift Clearing House
80
+ const clearingHousePublicKey = new web3_js_1.PublicKey(
81
+ sdkConfig.DRIFT_PROGRAM_ID
82
+ );
83
+ const clearingHouse = new __2.ClearingHouse({
84
+ connection,
85
+ wallet: provider.wallet,
86
+ programID: clearingHousePublicKey,
87
+ });
88
+ yield clearingHouse.subscribe();
89
+ // Set up Clearing House user client
90
+ const user = new __2.ClearingHouseUser({
91
+ clearingHouse,
92
+ userAccountPublicKey: yield clearingHouse.getUserAccountPublicKey(),
93
+ });
94
+ //// Check if clearing house account exists for the current wallet
95
+ const userAccountExists = yield user.exists();
96
+ if (!userAccountExists) {
97
+ //// Create a Clearing House account by Depositing some USDC ($10,000 in this case)
98
+ const depositAmount = new anchor_1.BN(10000).mul(__2.QUOTE_PRECISION);
99
+ yield clearingHouse.initializeUserAccountAndDepositCollateral(
100
+ depositAmount,
101
+ yield exports.getTokenAddress(
102
+ usdcTokenAddress.toString(),
103
+ wallet.publicKey.toString()
104
+ ),
105
+ banks_1.SpotMarkets['devnet'][0].marketIndex
106
+ );
107
+ }
108
+ yield user.subscribe();
109
+ // Get current price
110
+ const solMarketInfo = sdkConfig.PERP_MARKETS.find(
111
+ (market) => market.baseAssetSymbol === 'SOL'
112
+ );
113
+ const currentMarketPrice = __2.calculateMarkPrice(
114
+ clearingHouse.getMarketAccount(solMarketInfo.marketIndex),
115
+ undefined
116
+ );
117
+ const formattedPrice = __2.convertToNumber(
118
+ currentMarketPrice,
119
+ __2.PRICE_PRECISION
120
+ );
121
+ console.log(`Current Market Price is $${formattedPrice}`);
122
+ // Estimate the slippage for a $5000 LONG trade
123
+ const solMarketAccount = clearingHouse.getMarketAccount(
124
+ solMarketInfo.marketIndex
125
+ );
126
+ const longAmount = new anchor_1.BN(5000).mul(__2.QUOTE_PRECISION);
127
+ const slippage = __2.convertToNumber(
128
+ __2.calculateTradeSlippage(
129
+ __2.PositionDirection.LONG,
130
+ longAmount,
131
+ solMarketAccount,
132
+ 'quote',
133
+ undefined
134
+ )[0],
135
+ __2.PRICE_PRECISION
136
+ );
137
+ console.log(
138
+ `Slippage for a $5000 LONG on the SOL market would be $${slippage}`
139
+ );
140
+ // Make a $5000 LONG trade
141
+ yield clearingHouse.openPosition(
142
+ __2.PositionDirection.LONG,
143
+ longAmount,
144
+ solMarketInfo.marketIndex
145
+ );
146
+ console.log(`LONGED $5000 SOL`);
147
+ // Reduce the position by $2000
148
+ const reduceAmount = new anchor_1.BN(2000).mul(__2.QUOTE_PRECISION);
149
+ yield clearingHouse.openPosition(
150
+ __2.PositionDirection.SHORT,
151
+ reduceAmount,
152
+ solMarketInfo.marketIndex
153
+ );
154
+ // Close the rest of the position
155
+ yield clearingHouse.closePosition(solMarketInfo.marketIndex);
156
+ });
157
+ main();
package/src/math/amm.ts CHANGED
@@ -392,7 +392,7 @@ export function calculateEffectiveLeverage(
392
392
  reservePrice: BN,
393
393
  totalFeeMinusDistributions: BN
394
394
  ): number {
395
- // inventory skew
395
+ // vAMM skew
396
396
  const netBaseAssetValue = quoteAssetReserve
397
397
  .sub(terminalQuoteAssetReserve)
398
398
  .mul(pegMultiplier)
@@ -402,9 +402,13 @@ export function calculateEffectiveLeverage(
402
402
  .mul(reservePrice)
403
403
  .div(AMM_TO_QUOTE_PRECISION_RATIO.mul(PRICE_PRECISION));
404
404
 
405
+ const effectiveGap = Math.max(
406
+ 0,
407
+ localBaseAssetValue.sub(netBaseAssetValue).toNumber()
408
+ );
409
+
405
410
  const effectiveLeverage =
406
- localBaseAssetValue.sub(netBaseAssetValue).toNumber() /
407
- (Math.max(0, totalFeeMinusDistributions.toNumber()) + 1) +
411
+ effectiveGap / (Math.max(0, totalFeeMinusDistributions.toNumber()) + 1) +
408
412
  1 / QUOTE_PRECISION.toNumber();
409
413
 
410
414
  return effectiveLeverage;
@@ -580,7 +584,7 @@ export function calculateSpreadBN(
580
584
 
581
585
  if (baseAssetAmountWithAmm.gt(ZERO)) {
582
586
  longSpread *= spreadScale;
583
- shortSpread = Math.floor(longSpread);
587
+ longSpread = Math.floor(longSpread);
584
588
  } else {
585
589
  shortSpread *= spreadScale;
586
590
  shortSpread = Math.floor(shortSpread);
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTokenAccount = void 0;
4
+ const spl_token_1 = require("@solana/spl-token");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ function parseTokenAccount(data) {
7
+ const accountInfo = spl_token_1.AccountLayout.decode(data);
8
+ accountInfo.mint = new web3_js_1.PublicKey(accountInfo.mint);
9
+ accountInfo.owner = new web3_js_1.PublicKey(accountInfo.owner);
10
+ accountInfo.amount = spl_token_1.u64.fromBuffer(accountInfo.amount);
11
+ if (accountInfo.delegateOption === 0) {
12
+ accountInfo.delegate = null;
13
+ // eslint-disable-next-line new-cap
14
+ accountInfo.delegatedAmount = new spl_token_1.u64(0);
15
+ }
16
+ else {
17
+ accountInfo.delegate = new web3_js_1.PublicKey(accountInfo.delegate);
18
+ accountInfo.delegatedAmount = spl_token_1.u64.fromBuffer(accountInfo.delegatedAmount);
19
+ }
20
+ accountInfo.isInitialized = accountInfo.state !== 0;
21
+ accountInfo.isFrozen = accountInfo.state === 2;
22
+ if (accountInfo.isNativeOption === 1) {
23
+ accountInfo.rentExemptReserve = spl_token_1.u64.fromBuffer(accountInfo.isNative);
24
+ accountInfo.isNative = true;
25
+ }
26
+ else {
27
+ accountInfo.rentExemptReserve = null;
28
+ accountInfo.isNative = false;
29
+ }
30
+ if (accountInfo.closeAuthorityOption === 0) {
31
+ accountInfo.closeAuthority = null;
32
+ }
33
+ else {
34
+ accountInfo.closeAuthority = new web3_js_1.PublicKey(accountInfo.closeAuthority);
35
+ }
36
+ return accountInfo;
37
+ }
38
+ exports.parseTokenAccount = parseTokenAccount;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapInTx = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const COMPUTE_UNITS_DEFAULT = 200000;
6
+ function wrapInTx(instruction, computeUnits = 600000 // TODO, requires less code change
7
+ ) {
8
+ const tx = new web3_js_1.Transaction();
9
+ if (computeUnits != COMPUTE_UNITS_DEFAULT) {
10
+ tx.add(web3_js_1.ComputeBudgetProgram.requestUnits({
11
+ units: computeUnits,
12
+ additionalFee: 0,
13
+ }));
14
+ }
15
+ return tx.add(instruction);
16
+ }
17
+ exports.wrapInTx = wrapInTx;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.findComputeUnitConsumption = void 0;
13
+ function findComputeUnitConsumption(programId, connection, txSignature, commitment = 'confirmed') {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ const tx = yield connection.getTransaction(txSignature, { commitment });
16
+ const computeUnits = [];
17
+ const regex = new RegExp(`Program ${programId.toString()} consumed ([0-9]{0,6}) of ([0-9]{0,7}) compute units`);
18
+ tx.meta.logMessages.forEach((logMessage) => {
19
+ const match = logMessage.match(regex);
20
+ if (match && match[1]) {
21
+ computeUnits.push(match[1]);
22
+ }
23
+ });
24
+ return computeUnits;
25
+ });
26
+ }
27
+ exports.findComputeUnitConsumption = findComputeUnitConsumption;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTokenAddress = void 0;
4
+ const spl_token_1 = require("@solana/spl-token");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ const getTokenAddress = (mintAddress, userPubKey) => {
7
+ return spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, new web3_js_1.PublicKey(mintAddress), new web3_js_1.PublicKey(userPubKey));
8
+ };
9
+ exports.getTokenAddress = getTokenAddress;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promiseTimeout = void 0;
4
+ function promiseTimeout(promise, timeoutMs) {
5
+ let timeoutId;
6
+ const timeoutPromise = new Promise((resolve) => {
7
+ timeoutId = setTimeout(() => resolve(null), timeoutMs);
8
+ });
9
+ return Promise.race([promise, timeoutPromise]).then((result) => {
10
+ clearTimeout(timeoutId);
11
+ return result;
12
+ });
13
+ }
14
+ exports.promiseTimeout = promiseTimeout;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.estimateTps = void 0;
13
+ function estimateTps(programId, connection, failed) {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ let signatures = yield connection.getSignaturesForAddress(programId, undefined, 'finalized');
16
+ if (failed) {
17
+ signatures = signatures.filter((signature) => signature.err);
18
+ }
19
+ const numberOfSignatures = signatures.length;
20
+ if (numberOfSignatures === 0) {
21
+ return 0;
22
+ }
23
+ return (numberOfSignatures /
24
+ (signatures[0].blockTime - signatures[numberOfSignatures - 1].blockTime));
25
+ });
26
+ }
27
+ exports.estimateTps = estimateTps;
package/tests/amm/test.ts CHANGED
@@ -149,6 +149,41 @@ describe('AMM Tests', () => {
149
149
 
150
150
  });
151
151
 
152
+ it('Corner Case Spreads', () => {
153
+
154
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
155
+ // @ts-ignore
156
+ const terms2: AMMSpreadTerms = calculateSpreadBN(
157
+ 1000,
158
+ new BN(5555),
159
+ new BN(1131),
160
+ 20000,
161
+ new BN(1009967115003047),
162
+ new BN(1009811402660255),
163
+ new BN(13460124),
164
+ new BN(15328930153),
165
+ new BN(13667686),
166
+ new BN(1235066973),
167
+ new BN(88540713),
168
+ new BN(994097717724176),
169
+ new BN(974077854655784),
170
+ new BN(1014841945381208),
171
+ new BN(103320),
172
+ new BN(59975),
173
+ new BN(768323534),
174
+ new BN(243875031),
175
+ new BN(130017761029),
176
+ true
177
+ );
178
+
179
+ console.log(terms2);
180
+ assert(terms2.effectiveLeverageCapped<=1.000001);
181
+ assert(terms2.inventorySpreadScale==1.117371);
182
+ assert(terms2.longSpread==1263);
183
+ assert(terms2.shortSpread==6686);
184
+ });
185
+
186
+
152
187
  it('live update functions', () => {
153
188
 
154
189
  const mockAmm = mockPerpMarkets[0].amm;