@drift-labs/sdk 2.40.0-beta.13 → 2.40.0-beta.15

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/VERSION CHANGED
@@ -1 +1 @@
1
- 2.40.0-beta.13
1
+ 2.40.0-beta.15
@@ -75,11 +75,15 @@ class DLOBSubscriber {
75
75
  this.driftClient.getOracleDataForSpotMarket(marketIndex);
76
76
  }
77
77
  if (isPerp && includeVamm) {
78
+ if (fallbackL2Generators.length > 0) {
79
+ throw new Error('includeVamm can only be used if fallbackL2Generators is empty');
80
+ }
78
81
  fallbackL2Generators = [
79
82
  (0, orderBookLevels_1.getVammL2Generator)({
80
83
  marketAccount: this.driftClient.getPerpMarketAccount(marketIndex),
81
84
  oraclePriceData,
82
85
  numOrders: numVammOrders !== null && numVammOrders !== void 0 ? numVammOrders : depth,
86
+ topOfBookQuoteAmounts: orderBookLevels_1.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS,
83
87
  }),
84
88
  ];
85
89
  }
@@ -26,6 +26,7 @@ export type L3OrderBook = {
26
26
  asks: L3Level[];
27
27
  bids: L3Level[];
28
28
  };
29
+ export declare const DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS: any[];
29
30
  /**
30
31
  * Get an {@link Generator<L2Level>} generator from a {@link Generator<DLOBNode>}
31
32
  * @param dlobNodes e.g. {@link DLOB#getMakerLimitAsks} or {@link DLOB#getMakerLimitBids}
@@ -35,11 +36,12 @@ export type L3OrderBook = {
35
36
  export declare function getL2GeneratorFromDLOBNodes(dlobNodes: Generator<DLOBNode>, oraclePriceData: OraclePriceData, slot: number): Generator<L2Level>;
36
37
  export declare function mergeL2LevelGenerators(l2LevelGenerators: Generator<L2Level>[], compare: (a: L2Level, b: L2Level) => boolean): Generator<L2Level>;
37
38
  export declare function createL2Levels(generator: Generator<L2Level>, depth: number): L2Level[];
38
- export declare function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, }: {
39
+ export declare function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, topOfBookQuoteAmounts, }: {
39
40
  marketAccount: PerpMarketAccount;
40
41
  oraclePriceData: OraclePriceData;
41
42
  numOrders: number;
42
43
  now?: BN;
44
+ topOfBookQuoteAmounts?: BN[];
43
45
  }): L2OrderBookGenerator;
44
46
  export declare function groupL2(l2: L2OrderBook, grouping: BN, depth: number): L2OrderBook;
45
47
  export {};
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.groupL2 = exports.getVammL2Generator = exports.createL2Levels = exports.mergeL2LevelGenerators = exports.getL2GeneratorFromDLOBNodes = void 0;
3
+ exports.groupL2 = exports.getVammL2Generator = exports.createL2Levels = exports.mergeL2LevelGenerators = exports.getL2GeneratorFromDLOBNodes = exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = void 0;
4
4
  const __1 = require("..");
5
+ const assert_1 = require("../assert/assert");
6
+ exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = [
7
+ new __1.BN(500).mul(__1.QUOTE_PRECISION),
8
+ new __1.BN(1000).mul(__1.QUOTE_PRECISION),
9
+ new __1.BN(2000).mul(__1.QUOTE_PRECISION),
10
+ new __1.BN(5000).mul(__1.QUOTE_PRECISION),
11
+ ];
5
12
  /**
6
13
  * Get an {@link Generator<L2Level>} generator from a {@link Generator<DLOBNode>}
7
14
  * @param dlobNodes e.g. {@link DLOB#getMakerLimitAsks} or {@link DLOB#getMakerLimitBids}
@@ -78,13 +85,19 @@ function createL2Levels(generator, depth) {
78
85
  return levels;
79
86
  }
80
87
  exports.createL2Levels = createL2Levels;
81
- function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, }) {
88
+ function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, topOfBookQuoteAmounts, }) {
89
+ let numBaseOrders = numOrders;
90
+ if (topOfBookQuoteAmounts) {
91
+ numBaseOrders = numOrders - topOfBookQuoteAmounts.length;
92
+ (0, assert_1.assert)(topOfBookQuoteAmounts.length < numOrders);
93
+ }
82
94
  const updatedAmm = (0, __1.calculateUpdatedAMM)(marketAccount.amm, oraclePriceData);
83
95
  const [openBids, openAsks] = (0, __1.calculateMarketOpenBidAsk)(updatedAmm.baseAssetReserve, updatedAmm.minBaseAssetReserve, updatedAmm.maxBaseAssetReserve, updatedAmm.orderStepSize);
84
96
  now = now !== null && now !== void 0 ? now : new __1.BN(Date.now() / 1000);
85
97
  const [bidReserves, askReserves] = (0, __1.calculateSpreadReserves)(updatedAmm, oraclePriceData, now);
86
98
  let numBids = 0;
87
- const baseSize = openBids.div(new __1.BN(numOrders));
99
+ let topOfBookBidSize = __1.ZERO;
100
+ let bidSize = openBids.div(new __1.BN(numBaseOrders));
88
101
  const bidAmm = {
89
102
  baseAssetReserve: bidReserves.baseAssetReserve,
90
103
  quoteAssetReserve: bidReserves.quoteAssetReserve,
@@ -92,22 +105,45 @@ function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, })
92
105
  pegMultiplier: updatedAmm.pegMultiplier,
93
106
  };
94
107
  const getL2Bids = function* () {
95
- while (numBids < numOrders && baseSize.gt(__1.ZERO)) {
96
- const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, __1.calculateAmmReservesAfterSwap)(bidAmm, 'base', baseSize, __1.SwapDirection.ADD);
97
- const quoteSwapped = (0, __1.calculateQuoteAssetAmountSwapped)(bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), bidAmm.pegMultiplier, __1.SwapDirection.ADD);
98
- const price = quoteSwapped.mul(__1.BASE_PRECISION).div(baseSize);
108
+ while (numBids < numOrders && bidSize.gt(__1.ZERO)) {
109
+ let quoteSwapped = __1.ZERO;
110
+ let baseSwapped = __1.ZERO;
111
+ let [afterSwapQuoteReserves, afterSwapBaseReserves] = [__1.ZERO, __1.ZERO];
112
+ if (topOfBookQuoteAmounts && numBids < (topOfBookQuoteAmounts === null || topOfBookQuoteAmounts === void 0 ? void 0 : topOfBookQuoteAmounts.length)) {
113
+ const remainingBaseLiquidity = openBids.sub(topOfBookBidSize);
114
+ quoteSwapped = topOfBookQuoteAmounts[numBids];
115
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
116
+ (0, __1.calculateAmmReservesAfterSwap)(bidAmm, 'quote', quoteSwapped, __1.SwapDirection.REMOVE);
117
+ baseSwapped = bidAmm.baseAssetReserve.sub(afterSwapBaseReserves).abs();
118
+ if (remainingBaseLiquidity.lt(baseSwapped)) {
119
+ baseSwapped = remainingBaseLiquidity;
120
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
121
+ (0, __1.calculateAmmReservesAfterSwap)(bidAmm, 'base', baseSwapped, __1.SwapDirection.ADD);
122
+ quoteSwapped = (0, __1.calculateQuoteAssetAmountSwapped)(bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), bidAmm.pegMultiplier, __1.SwapDirection.ADD);
123
+ }
124
+ topOfBookBidSize = topOfBookBidSize.add(baseSwapped);
125
+ bidSize = openBids.sub(topOfBookBidSize).div(new __1.BN(numBaseOrders));
126
+ }
127
+ else {
128
+ baseSwapped = bidSize;
129
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
130
+ (0, __1.calculateAmmReservesAfterSwap)(bidAmm, 'base', baseSwapped, __1.SwapDirection.ADD);
131
+ quoteSwapped = (0, __1.calculateQuoteAssetAmountSwapped)(bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), bidAmm.pegMultiplier, __1.SwapDirection.ADD);
132
+ }
133
+ const price = quoteSwapped.mul(__1.BASE_PRECISION).div(baseSwapped);
99
134
  bidAmm.baseAssetReserve = afterSwapBaseReserves;
100
135
  bidAmm.quoteAssetReserve = afterSwapQuoteReserves;
101
136
  yield {
102
137
  price,
103
- size: baseSize,
104
- sources: { vamm: baseSize },
138
+ size: baseSwapped,
139
+ sources: { vamm: baseSwapped },
105
140
  };
106
141
  numBids++;
107
142
  }
108
143
  };
109
144
  let numAsks = 0;
110
- const askSize = openAsks.abs().div(new __1.BN(numOrders));
145
+ let topOfBookAskSize = __1.ZERO;
146
+ let askSize = openAsks.abs().div(new __1.BN(numBaseOrders));
111
147
  const askAmm = {
112
148
  baseAssetReserve: askReserves.baseAssetReserve,
113
149
  quoteAssetReserve: askReserves.quoteAssetReserve,
@@ -116,15 +152,42 @@ function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, })
116
152
  };
117
153
  const getL2Asks = function* () {
118
154
  while (numAsks < numOrders && askSize.gt(__1.ZERO)) {
119
- const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, __1.calculateAmmReservesAfterSwap)(askAmm, 'base', askSize, __1.SwapDirection.REMOVE);
120
- const quoteSwapped = (0, __1.calculateQuoteAssetAmountSwapped)(askAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), askAmm.pegMultiplier, __1.SwapDirection.REMOVE);
121
- const price = quoteSwapped.mul(__1.BASE_PRECISION).div(askSize);
155
+ let quoteSwapped = __1.ZERO;
156
+ let baseSwapped = __1.ZERO;
157
+ let [afterSwapQuoteReserves, afterSwapBaseReserves] = [__1.ZERO, __1.ZERO];
158
+ if (topOfBookQuoteAmounts && numAsks < (topOfBookQuoteAmounts === null || topOfBookQuoteAmounts === void 0 ? void 0 : topOfBookQuoteAmounts.length)) {
159
+ const remainingBaseLiquidity = openAsks
160
+ .mul(new __1.BN(-1))
161
+ .sub(topOfBookAskSize);
162
+ quoteSwapped = topOfBookQuoteAmounts[numAsks];
163
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
164
+ (0, __1.calculateAmmReservesAfterSwap)(askAmm, 'quote', quoteSwapped, __1.SwapDirection.ADD);
165
+ baseSwapped = askAmm.baseAssetReserve.sub(afterSwapBaseReserves).abs();
166
+ if (remainingBaseLiquidity.lt(baseSwapped)) {
167
+ baseSwapped = remainingBaseLiquidity;
168
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
169
+ (0, __1.calculateAmmReservesAfterSwap)(bidAmm, 'base', baseSwapped, __1.SwapDirection.REMOVE);
170
+ quoteSwapped = (0, __1.calculateQuoteAssetAmountSwapped)(bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), bidAmm.pegMultiplier, __1.SwapDirection.REMOVE);
171
+ }
172
+ topOfBookAskSize = topOfBookAskSize.add(baseSwapped);
173
+ askSize = openAsks
174
+ .abs()
175
+ .sub(topOfBookAskSize)
176
+ .div(new __1.BN(numBaseOrders));
177
+ }
178
+ else {
179
+ baseSwapped = askSize;
180
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
181
+ (0, __1.calculateAmmReservesAfterSwap)(askAmm, 'base', askSize, __1.SwapDirection.REMOVE);
182
+ quoteSwapped = (0, __1.calculateQuoteAssetAmountSwapped)(askAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), askAmm.pegMultiplier, __1.SwapDirection.REMOVE);
183
+ }
184
+ const price = quoteSwapped.mul(__1.BASE_PRECISION).div(baseSwapped);
122
185
  askAmm.baseAssetReserve = afterSwapBaseReserves;
123
186
  askAmm.quoteAssetReserve = afterSwapQuoteReserves;
124
187
  yield {
125
188
  price,
126
- size: askSize,
127
- sources: { vamm: baseSize },
189
+ size: baseSwapped,
190
+ sources: { vamm: baseSwapped },
128
191
  };
129
192
  numAsks++;
130
193
  }
@@ -229,7 +229,7 @@ export declare class JupiterClient {
229
229
  * @param swapMode the swap mode (ExactIn or ExactOut)
230
230
  * @param onlyDirectRoutes whether to only return direct routes
231
231
  */
232
- getQuote({ inputMint, outputMint, amount, maxAccounts, // 52 is an estimated amount with buffer
232
+ getQuote({ inputMint, outputMint, amount, maxAccounts, // 50 is an estimated amount with buffer
233
233
  slippageBps, swapMode, onlyDirectRoutes, excludeDexes, }: {
234
234
  inputMint: PublicKey;
235
235
  outputMint: PublicKey;
@@ -43,7 +43,7 @@ class JupiterClient {
43
43
  * @param swapMode the swap mode (ExactIn or ExactOut)
44
44
  * @param onlyDirectRoutes whether to only return direct routes
45
45
  */
46
- async getQuote({ inputMint, outputMint, amount, maxAccounts = 52, // 52 is an estimated amount with buffer
46
+ async getQuote({ inputMint, outputMint, amount, maxAccounts = 50, // 50 is an estimated amount with buffer
47
47
  slippageBps = 50, swapMode = 'ExactIn', onlyDirectRoutes = false, excludeDexes = [], }) {
48
48
  const params = new URLSearchParams({
49
49
  inputMint: inputMint.toString(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.40.0-beta.13",
3
+ "version": "2.40.0-beta.15",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -44,6 +44,7 @@
44
44
  "uuid": "^8.3.2"
45
45
  },
46
46
  "devDependencies": {
47
+ "@types/big.js": "^6.2.0",
47
48
  "@types/chai": "^4.3.1",
48
49
  "@types/jest": "^28.1.3",
49
50
  "@types/mocha": "^9.1.1",
@@ -10,6 +10,7 @@ import {
10
10
  import { DriftClient } from '../driftClient';
11
11
  import { isVariant, MarketType } from '../types';
12
12
  import {
13
+ DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS,
13
14
  getVammL2Generator,
14
15
  L2OrderBook,
15
16
  L2OrderBookGenerator,
@@ -120,11 +121,18 @@ export class DLOBSubscriber {
120
121
  }
121
122
 
122
123
  if (isPerp && includeVamm) {
124
+ if (fallbackL2Generators.length > 0) {
125
+ throw new Error(
126
+ 'includeVamm can only be used if fallbackL2Generators is empty'
127
+ );
128
+ }
129
+
123
130
  fallbackL2Generators = [
124
131
  getVammL2Generator({
125
132
  marketAccount: this.driftClient.getPerpMarketAccount(marketIndex),
126
133
  oraclePriceData,
127
134
  numOrders: numVammOrders ?? depth,
135
+ topOfBookQuoteAmounts: DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS,
128
136
  }),
129
137
  ];
130
138
  }
@@ -10,11 +10,13 @@ import {
10
10
  OraclePriceData,
11
11
  PerpMarketAccount,
12
12
  PositionDirection,
13
+ QUOTE_PRECISION,
13
14
  standardizePrice,
14
15
  SwapDirection,
15
16
  ZERO,
16
17
  } from '..';
17
18
  import { PublicKey } from '@solana/web3.js';
19
+ import { assert } from '../assert/assert';
18
20
 
19
21
  type liquiditySource = 'serum' | 'vamm' | 'dlob' | 'phoenix';
20
22
 
@@ -46,6 +48,13 @@ export type L3OrderBook = {
46
48
  bids: L3Level[];
47
49
  };
48
50
 
51
+ export const DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = [
52
+ new BN(500).mul(QUOTE_PRECISION),
53
+ new BN(1000).mul(QUOTE_PRECISION),
54
+ new BN(2000).mul(QUOTE_PRECISION),
55
+ new BN(5000).mul(QUOTE_PRECISION),
56
+ ];
57
+
49
58
  /**
50
59
  * Get an {@link Generator<L2Level>} generator from a {@link Generator<DLOBNode>}
51
60
  * @param dlobNodes e.g. {@link DLOB#getMakerLimitAsks} or {@link DLOB#getMakerLimitBids}
@@ -139,12 +148,20 @@ export function getVammL2Generator({
139
148
  oraclePriceData,
140
149
  numOrders,
141
150
  now,
151
+ topOfBookQuoteAmounts,
142
152
  }: {
143
153
  marketAccount: PerpMarketAccount;
144
154
  oraclePriceData: OraclePriceData;
145
155
  numOrders: number;
146
156
  now?: BN;
157
+ topOfBookQuoteAmounts?: BN[];
147
158
  }): L2OrderBookGenerator {
159
+ let numBaseOrders = numOrders;
160
+ if (topOfBookQuoteAmounts) {
161
+ numBaseOrders = numOrders - topOfBookQuoteAmounts.length;
162
+ assert(topOfBookQuoteAmounts.length < numOrders);
163
+ }
164
+
148
165
  const updatedAmm = calculateUpdatedAMM(marketAccount.amm, oraclePriceData);
149
166
 
150
167
  const [openBids, openAsks] = calculateMarketOpenBidAsk(
@@ -162,38 +179,78 @@ export function getVammL2Generator({
162
179
  );
163
180
 
164
181
  let numBids = 0;
165
- const baseSize = openBids.div(new BN(numOrders));
182
+
183
+ let topOfBookBidSize = ZERO;
184
+ let bidSize = openBids.div(new BN(numBaseOrders));
166
185
  const bidAmm = {
167
186
  baseAssetReserve: bidReserves.baseAssetReserve,
168
187
  quoteAssetReserve: bidReserves.quoteAssetReserve,
169
188
  sqrtK: updatedAmm.sqrtK,
170
189
  pegMultiplier: updatedAmm.pegMultiplier,
171
190
  };
191
+
172
192
  const getL2Bids = function* () {
173
- while (numBids < numOrders && baseSize.gt(ZERO)) {
174
- const [afterSwapQuoteReserves, afterSwapBaseReserves] =
175
- calculateAmmReservesAfterSwap(
176
- bidAmm,
177
- 'base',
178
- baseSize,
193
+ while (numBids < numOrders && bidSize.gt(ZERO)) {
194
+ let quoteSwapped = ZERO;
195
+ let baseSwapped = ZERO;
196
+ let [afterSwapQuoteReserves, afterSwapBaseReserves] = [ZERO, ZERO];
197
+
198
+ if (topOfBookQuoteAmounts && numBids < topOfBookQuoteAmounts?.length) {
199
+ const remainingBaseLiquidity = openBids.sub(topOfBookBidSize);
200
+ quoteSwapped = topOfBookQuoteAmounts[numBids];
201
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
202
+ calculateAmmReservesAfterSwap(
203
+ bidAmm,
204
+ 'quote',
205
+ quoteSwapped,
206
+ SwapDirection.REMOVE
207
+ );
208
+
209
+ baseSwapped = bidAmm.baseAssetReserve.sub(afterSwapBaseReserves).abs();
210
+ if (remainingBaseLiquidity.lt(baseSwapped)) {
211
+ baseSwapped = remainingBaseLiquidity;
212
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
213
+ calculateAmmReservesAfterSwap(
214
+ bidAmm,
215
+ 'base',
216
+ baseSwapped,
217
+ SwapDirection.ADD
218
+ );
219
+
220
+ quoteSwapped = calculateQuoteAssetAmountSwapped(
221
+ bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
222
+ bidAmm.pegMultiplier,
223
+ SwapDirection.ADD
224
+ );
225
+ }
226
+ topOfBookBidSize = topOfBookBidSize.add(baseSwapped);
227
+ bidSize = openBids.sub(topOfBookBidSize).div(new BN(numBaseOrders));
228
+ } else {
229
+ baseSwapped = bidSize;
230
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
231
+ calculateAmmReservesAfterSwap(
232
+ bidAmm,
233
+ 'base',
234
+ baseSwapped,
235
+ SwapDirection.ADD
236
+ );
237
+
238
+ quoteSwapped = calculateQuoteAssetAmountSwapped(
239
+ bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
240
+ bidAmm.pegMultiplier,
179
241
  SwapDirection.ADD
180
242
  );
243
+ }
181
244
 
182
- const quoteSwapped = calculateQuoteAssetAmountSwapped(
183
- bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
184
- bidAmm.pegMultiplier,
185
- SwapDirection.ADD
186
- );
187
-
188
- const price = quoteSwapped.mul(BASE_PRECISION).div(baseSize);
245
+ const price = quoteSwapped.mul(BASE_PRECISION).div(baseSwapped);
189
246
 
190
247
  bidAmm.baseAssetReserve = afterSwapBaseReserves;
191
248
  bidAmm.quoteAssetReserve = afterSwapQuoteReserves;
192
249
 
193
250
  yield {
194
251
  price,
195
- size: baseSize,
196
- sources: { vamm: baseSize },
252
+ size: baseSwapped,
253
+ sources: { vamm: baseSwapped },
197
254
  };
198
255
 
199
256
  numBids++;
@@ -201,38 +258,82 @@ export function getVammL2Generator({
201
258
  };
202
259
 
203
260
  let numAsks = 0;
204
- const askSize = openAsks.abs().div(new BN(numOrders));
261
+ let topOfBookAskSize = ZERO;
262
+ let askSize = openAsks.abs().div(new BN(numBaseOrders));
205
263
  const askAmm = {
206
264
  baseAssetReserve: askReserves.baseAssetReserve,
207
265
  quoteAssetReserve: askReserves.quoteAssetReserve,
208
266
  sqrtK: updatedAmm.sqrtK,
209
267
  pegMultiplier: updatedAmm.pegMultiplier,
210
268
  };
269
+
211
270
  const getL2Asks = function* () {
212
271
  while (numAsks < numOrders && askSize.gt(ZERO)) {
213
- const [afterSwapQuoteReserves, afterSwapBaseReserves] =
214
- calculateAmmReservesAfterSwap(
215
- askAmm,
216
- 'base',
217
- askSize,
272
+ let quoteSwapped: BN = ZERO;
273
+ let baseSwapped: BN = ZERO;
274
+ let [afterSwapQuoteReserves, afterSwapBaseReserves] = [ZERO, ZERO];
275
+
276
+ if (topOfBookQuoteAmounts && numAsks < topOfBookQuoteAmounts?.length) {
277
+ const remainingBaseLiquidity = openAsks
278
+ .mul(new BN(-1))
279
+ .sub(topOfBookAskSize);
280
+ quoteSwapped = topOfBookQuoteAmounts[numAsks];
281
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
282
+ calculateAmmReservesAfterSwap(
283
+ askAmm,
284
+ 'quote',
285
+ quoteSwapped,
286
+ SwapDirection.ADD
287
+ );
288
+
289
+ baseSwapped = askAmm.baseAssetReserve.sub(afterSwapBaseReserves).abs();
290
+ if (remainingBaseLiquidity.lt(baseSwapped)) {
291
+ baseSwapped = remainingBaseLiquidity;
292
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
293
+ calculateAmmReservesAfterSwap(
294
+ bidAmm,
295
+ 'base',
296
+ baseSwapped,
297
+ SwapDirection.REMOVE
298
+ );
299
+
300
+ quoteSwapped = calculateQuoteAssetAmountSwapped(
301
+ bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
302
+ bidAmm.pegMultiplier,
303
+ SwapDirection.REMOVE
304
+ );
305
+ }
306
+ topOfBookAskSize = topOfBookAskSize.add(baseSwapped);
307
+ askSize = openAsks
308
+ .abs()
309
+ .sub(topOfBookAskSize)
310
+ .div(new BN(numBaseOrders));
311
+ } else {
312
+ baseSwapped = askSize;
313
+ [afterSwapQuoteReserves, afterSwapBaseReserves] =
314
+ calculateAmmReservesAfterSwap(
315
+ askAmm,
316
+ 'base',
317
+ askSize,
318
+ SwapDirection.REMOVE
319
+ );
320
+
321
+ quoteSwapped = calculateQuoteAssetAmountSwapped(
322
+ askAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
323
+ askAmm.pegMultiplier,
218
324
  SwapDirection.REMOVE
219
325
  );
326
+ }
220
327
 
221
- const quoteSwapped = calculateQuoteAssetAmountSwapped(
222
- askAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
223
- askAmm.pegMultiplier,
224
- SwapDirection.REMOVE
225
- );
226
-
227
- const price = quoteSwapped.mul(BASE_PRECISION).div(askSize);
328
+ const price = quoteSwapped.mul(BASE_PRECISION).div(baseSwapped);
228
329
 
229
330
  askAmm.baseAssetReserve = afterSwapBaseReserves;
230
331
  askAmm.quoteAssetReserve = afterSwapQuoteReserves;
231
332
 
232
333
  yield {
233
334
  price,
234
- size: askSize,
235
- sources: { vamm: baseSize },
335
+ size: baseSwapped,
336
+ sources: { vamm: baseSwapped },
236
337
  };
237
338
 
238
339
  numAsks++;
@@ -275,7 +275,7 @@ export class JupiterClient {
275
275
  inputMint,
276
276
  outputMint,
277
277
  amount,
278
- maxAccounts = 52, // 52 is an estimated amount with buffer
278
+ maxAccounts = 50, // 50 is an estimated amount with buffer
279
279
  slippageBps = 50,
280
280
  swapMode = 'ExactIn',
281
281
  onlyDirectRoutes = false,
package/tests/amm/test.ts CHANGED
@@ -13,6 +13,12 @@ import {
13
13
  calculateAllEstimatedFundingRate,
14
14
  calculateLongShortFundingRateAndLiveTwaps,
15
15
  OraclePriceData,
16
+ getVammL2Generator,
17
+ BASE_PRECISION,
18
+ PerpMarketAccount,
19
+ L2Level,
20
+ calculateUpdatedAMM,
21
+ calculateMarketOpenBidAsk,
16
22
  } from '../../src';
17
23
  import { mockPerpMarkets } from '../dlob/helpers';
18
24
 
@@ -552,4 +558,400 @@ describe('AMM Tests', () => {
552
558
  assert(est1.eq(est2));
553
559
  assert(est2.eq(new BN('-1550')));
554
560
  });
561
+
562
+ it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => {
563
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
564
+
565
+ const mockMarket1: PerpMarketAccount = myMockPerpMarkets[0];
566
+ const cc = 38104569;
567
+ mockMarket1.amm.baseAssetReserve = new BN(cc).mul(BASE_PRECISION);
568
+ mockMarket1.amm.maxBaseAssetReserve = mockMarket1.amm.baseAssetReserve.add(
569
+ new BN(1234835)
570
+ );
571
+ mockMarket1.amm.minBaseAssetReserve = mockMarket1.amm.baseAssetReserve.sub(BASE_PRECISION);
572
+ mockMarket1.amm.quoteAssetReserve = new BN(cc).mul(BASE_PRECISION);
573
+ mockMarket1.amm.pegMultiplier = new BN(18.32 * PEG_PRECISION.toNumber());
574
+ mockMarket1.amm.sqrtK = new BN(cc).mul(BASE_PRECISION);
575
+
576
+ const now = new BN(1688881915);
577
+
578
+ const oraclePriceData: OraclePriceData = {
579
+ price: new BN(18.624 * PRICE_PRECISION.toNumber()),
580
+ slot: new BN(0),
581
+ confidence: new BN(1),
582
+ hasSufficientNumberOfDataPoints: true,
583
+ };
584
+ mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(
585
+ 18.5535 * PRICE_PRECISION.toNumber()
586
+ );
587
+
588
+ const updatedAmm = calculateUpdatedAMM(mockMarket1.amm, oraclePriceData);
589
+
590
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
591
+ updatedAmm.baseAssetReserve,
592
+ updatedAmm.minBaseAssetReserve,
593
+ updatedAmm.maxBaseAssetReserve,
594
+ updatedAmm.orderStepSize
595
+ );
596
+
597
+ const generator = getVammL2Generator({
598
+ marketAccount: mockMarket1,
599
+ oraclePriceData,
600
+ numOrders: 10,
601
+ now,
602
+ topOfBookQuoteAmounts: [],
603
+ });
604
+
605
+ const bids = Array.from(generator.getL2Bids());
606
+ // console.log(bids);
607
+
608
+ const totalBidSize = bids.reduce((total: BN, order: L2Level) => {
609
+ return total.add(order.size);
610
+ }, ZERO);
611
+
612
+ console.log(
613
+ 'totalBidSize:',
614
+ totalBidSize.toString(),
615
+ 'openBids:',
616
+ openBids.toString()
617
+ );
618
+ assert(totalBidSize.sub(openBids).abs().lt(new BN(10))); // smol err
619
+ assert(totalBidSize.sub(openBids).lt(ZERO)); // under estimation
620
+
621
+ const asks = Array.from(generator.getL2Asks());
622
+ // console.log(asks);
623
+
624
+ const totalAskSize = asks.reduce((total: BN, order: L2Level) => {
625
+ return total.add(order.size);
626
+ }, ZERO);
627
+ console.log(
628
+ 'totalAskSize:',
629
+ totalAskSize.toString(),
630
+ 'openAsks:',
631
+ openAsks.toString()
632
+ );
633
+ assert(totalAskSize.sub(openAsks.abs()).lte(new BN(5))); // only tiny rounding errors
634
+ });
635
+
636
+ it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders)', async () => {
637
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
638
+
639
+ const mockMarket1: PerpMarketAccount = myMockPerpMarkets[0];
640
+ const cc = 38104569;
641
+ mockMarket1.amm.baseAssetReserve = new BN(cc).mul(BASE_PRECISION);
642
+ mockMarket1.amm.maxBaseAssetReserve = mockMarket1.amm.baseAssetReserve.mul(
643
+ new BN(2)
644
+ );
645
+ mockMarket1.amm.minBaseAssetReserve = mockMarket1.amm.baseAssetReserve.div(
646
+ new BN(2)
647
+ );
648
+ mockMarket1.amm.quoteAssetReserve = new BN(cc).mul(BASE_PRECISION);
649
+ mockMarket1.amm.pegMultiplier = new BN(18.32 * PEG_PRECISION.toNumber());
650
+ mockMarket1.amm.sqrtK = new BN(cc).mul(BASE_PRECISION);
651
+
652
+ const now = new BN(1688881915);
653
+
654
+ const oraclePriceData: OraclePriceData = {
655
+ price: new BN(18.624 * PRICE_PRECISION.toNumber()),
656
+ slot: new BN(0),
657
+ confidence: new BN(1),
658
+ hasSufficientNumberOfDataPoints: true,
659
+ };
660
+ mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(
661
+ 18.5535 * PRICE_PRECISION.toNumber()
662
+ );
663
+
664
+ const updatedAmm = calculateUpdatedAMM(mockMarket1.amm, oraclePriceData);
665
+
666
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
667
+ updatedAmm.baseAssetReserve,
668
+ updatedAmm.minBaseAssetReserve,
669
+ updatedAmm.maxBaseAssetReserve,
670
+ updatedAmm.orderStepSize
671
+ );
672
+
673
+ const generator = getVammL2Generator({
674
+ marketAccount: mockMarket1,
675
+ oraclePriceData,
676
+ numOrders: 10,
677
+ now,
678
+ topOfBookQuoteAmounts: [],
679
+ });
680
+
681
+ const bids = Array.from(generator.getL2Bids());
682
+ // console.log(bids);
683
+
684
+ const totalBidSize = bids.reduce((total: BN, order: L2Level) => {
685
+ return total.add(order.size);
686
+ }, ZERO);
687
+
688
+ console.log(
689
+ 'totalBidSize:',
690
+ totalBidSize.toString(),
691
+ 'openBids:',
692
+ openBids.toString()
693
+ );
694
+ assert(totalBidSize.eq(openBids));
695
+
696
+ const asks = Array.from(generator.getL2Asks());
697
+ // console.log(asks);
698
+
699
+ const totalAskSize = asks.reduce((total: BN, order: L2Level) => {
700
+ return total.add(order.size);
701
+ }, ZERO);
702
+ console.log(
703
+ 'totalAskSize:',
704
+ totalAskSize.toString(),
705
+ 'openAsks:',
706
+ openAsks.toString()
707
+ );
708
+ assert(totalAskSize.sub(openAsks.abs()).lte(new BN(5))); // only tiny rounding errors
709
+ });
710
+
711
+ it('orderbook L2 gen (4 topOfBookQuoteAmounts, 10 numOrders)', async () => {
712
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
713
+
714
+ const mockMarket1: PerpMarketAccount = myMockPerpMarkets[0];
715
+ const cc = 38104569;
716
+ mockMarket1.amm.baseAssetReserve = new BN(cc).mul(BASE_PRECISION);
717
+ mockMarket1.amm.maxBaseAssetReserve = mockMarket1.amm.baseAssetReserve.mul(
718
+ new BN(2)
719
+ );
720
+ mockMarket1.amm.minBaseAssetReserve = mockMarket1.amm.baseAssetReserve.div(
721
+ new BN(2)
722
+ );
723
+ mockMarket1.amm.quoteAssetReserve = new BN(cc).mul(BASE_PRECISION);
724
+ mockMarket1.amm.pegMultiplier = new BN(18.32 * PEG_PRECISION.toNumber());
725
+ mockMarket1.amm.sqrtK = new BN(cc).mul(BASE_PRECISION);
726
+
727
+ const now = new BN(1688881915);
728
+
729
+ const oraclePriceData: OraclePriceData = {
730
+ price: new BN(18.624 * PRICE_PRECISION.toNumber()),
731
+ slot: new BN(0),
732
+ confidence: new BN(1),
733
+ hasSufficientNumberOfDataPoints: true,
734
+ };
735
+ mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(
736
+ 18.5535 * PRICE_PRECISION.toNumber()
737
+ );
738
+
739
+ const updatedAmm = calculateUpdatedAMM(mockMarket1.amm, oraclePriceData);
740
+
741
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
742
+ updatedAmm.baseAssetReserve,
743
+ updatedAmm.minBaseAssetReserve,
744
+ updatedAmm.maxBaseAssetReserve,
745
+ updatedAmm.orderStepSize
746
+ );
747
+
748
+ assert(!openAsks.eq(openBids));
749
+
750
+ const generator = getVammL2Generator({
751
+ marketAccount: mockMarket1,
752
+ oraclePriceData,
753
+ numOrders: 10,
754
+ now,
755
+ topOfBookQuoteAmounts: [
756
+ new BN(10).mul(QUOTE_PRECISION),
757
+ new BN(100).mul(QUOTE_PRECISION),
758
+ new BN(1000).mul(QUOTE_PRECISION),
759
+ new BN(10000).mul(QUOTE_PRECISION),
760
+ ],
761
+ });
762
+
763
+ const bids = Array.from(generator.getL2Bids());
764
+ // console.log(bids);
765
+
766
+ const totalBidSize = bids.reduce((total: BN, order: L2Level) => {
767
+ return total.add(order.size);
768
+ }, ZERO);
769
+
770
+ console.log(
771
+ 'totalBidSize:',
772
+ totalBidSize.toString(),
773
+ 'openBids:',
774
+ openBids.toString()
775
+ );
776
+ assert(totalBidSize.eq(openBids));
777
+
778
+ const asks = Array.from(generator.getL2Asks());
779
+ // console.log(asks);
780
+
781
+ const totalAskSize = asks.reduce((total: BN, order: L2Level) => {
782
+ return total.add(order.size);
783
+ }, ZERO);
784
+ console.log(
785
+ 'totalAskSize:',
786
+ totalAskSize.toString(),
787
+ 'openAsks:',
788
+ openAsks.toString()
789
+ );
790
+ assert(totalAskSize.sub(openAsks.abs()).lte(new BN(5))); // only tiny rounding errors
791
+ });
792
+
793
+
794
+ it('orderbook L2 gen (4 topOfBookQuoteAmounts, 10 numOrders, low bid liquidity)', async () => {
795
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
796
+
797
+ const mockMarket1: PerpMarketAccount = myMockPerpMarkets[0];
798
+ const cc = 38104569;
799
+ mockMarket1.amm.baseAssetReserve = new BN(cc).mul(BASE_PRECISION);
800
+ mockMarket1.amm.maxBaseAssetReserve = mockMarket1.amm.baseAssetReserve.add(BASE_PRECISION); // only 1 base
801
+ mockMarket1.amm.minBaseAssetReserve = mockMarket1.amm.baseAssetReserve.div(
802
+ new BN(2)
803
+ );
804
+ mockMarket1.amm.quoteAssetReserve = new BN(cc).mul(BASE_PRECISION);
805
+ mockMarket1.amm.pegMultiplier = new BN(18.32 * PEG_PRECISION.toNumber());
806
+ mockMarket1.amm.sqrtK = new BN(cc).mul(BASE_PRECISION);
807
+
808
+ const now = new BN(1688881915);
809
+
810
+ const oraclePriceData: OraclePriceData = {
811
+ price: new BN(18.624 * PRICE_PRECISION.toNumber()),
812
+ slot: new BN(0),
813
+ confidence: new BN(1),
814
+ hasSufficientNumberOfDataPoints: true,
815
+ };
816
+ mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(
817
+ 18.5535 * PRICE_PRECISION.toNumber()
818
+ );
819
+
820
+ const updatedAmm = calculateUpdatedAMM(mockMarket1.amm, oraclePriceData);
821
+
822
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
823
+ updatedAmm.baseAssetReserve,
824
+ updatedAmm.minBaseAssetReserve,
825
+ updatedAmm.maxBaseAssetReserve,
826
+ updatedAmm.orderStepSize
827
+ );
828
+
829
+ assert(!openAsks.eq(openBids));
830
+
831
+ const generator = getVammL2Generator({
832
+ marketAccount: mockMarket1,
833
+ oraclePriceData,
834
+ numOrders: 10,
835
+ now,
836
+ topOfBookQuoteAmounts: [
837
+ new BN(10).mul(QUOTE_PRECISION),
838
+ new BN(100).mul(QUOTE_PRECISION),
839
+ new BN(1000).mul(QUOTE_PRECISION),
840
+ new BN(10000).mul(QUOTE_PRECISION),
841
+ ],
842
+ });
843
+
844
+ const bids = Array.from(generator.getL2Bids());
845
+ assert(bids.length == 2);
846
+ console.log(bids[0].size.toString());
847
+ console.log(bids[1].size.toString());
848
+
849
+ const totalBidSize = bids.reduce((total: BN, order: L2Level) => {
850
+ return total.add(order.size);
851
+ }, ZERO);
852
+
853
+ console.log(
854
+ 'totalBidSize:',
855
+ totalBidSize.toString(),
856
+ 'openBids:',
857
+ openBids.toString()
858
+ );
859
+ assert(totalBidSize.eq(openBids));
860
+
861
+ const asks = Array.from(generator.getL2Asks());
862
+ // console.log(asks);
863
+
864
+ const totalAskSize = asks.reduce((total: BN, order: L2Level) => {
865
+ return total.add(order.size);
866
+ }, ZERO);
867
+ console.log(
868
+ 'totalAskSize:',
869
+ totalAskSize.toString(),
870
+ 'openAsks:',
871
+ openAsks.toString()
872
+ );
873
+ assert(totalAskSize.sub(openAsks.abs()).lte(new BN(5))); // only tiny rounding errors
874
+ });
875
+
876
+
877
+ it('orderbook L2 gen (4 topOfBookQuoteAmounts, 10 numOrders, low ask liquidity)', async () => {
878
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
879
+
880
+ const mockMarket1: PerpMarketAccount = myMockPerpMarkets[0];
881
+ const cc = 38104569;
882
+ mockMarket1.amm.baseAssetReserve = new BN(cc).mul(BASE_PRECISION);
883
+ mockMarket1.amm.maxBaseAssetReserve = mockMarket1.amm.baseAssetReserve.add(BASE_PRECISION.mul(new BN(1000))); // 1000 base
884
+ mockMarket1.amm.minBaseAssetReserve = mockMarket1.amm.baseAssetReserve.sub(
885
+ BASE_PRECISION.div(new BN(2))
886
+ ); // only .5 base
887
+ mockMarket1.amm.quoteAssetReserve = new BN(cc).mul(BASE_PRECISION);
888
+ mockMarket1.amm.pegMultiplier = new BN(18.32 * PEG_PRECISION.toNumber());
889
+ mockMarket1.amm.sqrtK = new BN(cc).mul(BASE_PRECISION);
890
+
891
+ const now = new BN(1688881915);
892
+
893
+ const oraclePriceData: OraclePriceData = {
894
+ price: new BN(18.624 * PRICE_PRECISION.toNumber()),
895
+ slot: new BN(0),
896
+ confidence: new BN(1),
897
+ hasSufficientNumberOfDataPoints: true,
898
+ };
899
+ mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(
900
+ 18.5535 * PRICE_PRECISION.toNumber()
901
+ );
902
+
903
+ const updatedAmm = calculateUpdatedAMM(mockMarket1.amm, oraclePriceData);
904
+
905
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
906
+ updatedAmm.baseAssetReserve,
907
+ updatedAmm.minBaseAssetReserve,
908
+ updatedAmm.maxBaseAssetReserve,
909
+ updatedAmm.orderStepSize
910
+ );
911
+
912
+ assert(!openAsks.eq(openBids));
913
+
914
+ const generator = getVammL2Generator({
915
+ marketAccount: mockMarket1,
916
+ oraclePriceData,
917
+ numOrders: 10,
918
+ now,
919
+ topOfBookQuoteAmounts: [
920
+ new BN(10).mul(QUOTE_PRECISION),
921
+ new BN(100).mul(QUOTE_PRECISION),
922
+ new BN(1000).mul(QUOTE_PRECISION),
923
+ new BN(10000).mul(QUOTE_PRECISION),
924
+ ],
925
+ });
926
+
927
+ const bids = Array.from(generator.getL2Bids());
928
+
929
+ const totalBidSize = bids.reduce((total: BN, order: L2Level) => {
930
+ return total.add(order.size);
931
+ }, ZERO);
932
+
933
+ console.log(
934
+ 'totalBidSize:',
935
+ totalBidSize.toString(),
936
+ 'openBids:',
937
+ openBids.toString()
938
+ );
939
+ assert(totalBidSize.sub(openBids).abs().lt(new BN(5)));
940
+
941
+ const asks = Array.from(generator.getL2Asks());
942
+ // console.log(asks);
943
+
944
+ assert(asks.length == 1);
945
+ console.log(asks[0].size.toString());
946
+ const totalAskSize = asks.reduce((total: BN, order: L2Level) => {
947
+ return total.add(order.size);
948
+ }, ZERO);
949
+ console.log(
950
+ 'totalAskSize:',
951
+ totalAskSize.toString(),
952
+ 'openAsks:',
953
+ openAsks.toString()
954
+ );
955
+ assert(totalAskSize.sub(openAsks.abs()).lte(new BN(5))); // only tiny rounding errors
956
+ });
555
957
  });