@drift-labs/sdk 2.40.0-beta.9 → 2.41.0-beta.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.
Files changed (40) hide show
  1. package/VERSION +1 -1
  2. package/bun.lockb +0 -0
  3. package/lib/addresses/pda.d.ts +1 -0
  4. package/lib/addresses/pda.js +5 -1
  5. package/lib/adminClient.d.ts +2 -0
  6. package/lib/adminClient.js +20 -0
  7. package/lib/constants/spotMarkets.js +1 -1
  8. package/lib/dlob/DLOB.d.ts +2 -6
  9. package/lib/dlob/DLOB.js +9 -11
  10. package/lib/dlob/DLOBSubscriber.js +4 -7
  11. package/lib/dlob/orderBookLevels.d.ts +4 -2
  12. package/lib/dlob/orderBookLevels.js +79 -16
  13. package/lib/factory/bigNum.js +4 -2
  14. package/lib/idl/drift.json +171 -2
  15. package/lib/jupiter/jupiterClient.d.ts +3 -2
  16. package/lib/jupiter/jupiterClient.js +3 -2
  17. package/lib/math/auction.d.ts +12 -1
  18. package/lib/math/auction.js +22 -1
  19. package/lib/math/market.js +2 -4
  20. package/lib/math/superStake.js +1 -1
  21. package/lib/math/trade.js +2 -4
  22. package/lib/orderSubscriber/WebsocketSubscription.js +2 -0
  23. package/package.json +2 -1
  24. package/src/addresses/pda.ts +9 -0
  25. package/src/adminClient.ts +32 -0
  26. package/src/constants/spotMarkets.ts +1 -1
  27. package/src/dlob/DLOB.ts +9 -32
  28. package/src/dlob/DLOBSubscriber.ts +8 -7
  29. package/src/dlob/orderBookLevels.ts +133 -32
  30. package/src/factory/bigNum.ts +2 -0
  31. package/src/idl/drift.json +171 -2
  32. package/src/jupiter/jupiterClient.ts +4 -1
  33. package/src/math/auction.ts +36 -2
  34. package/src/math/market.ts +4 -9
  35. package/src/math/superStake.ts +2 -2
  36. package/src/math/trade.ts +3 -11
  37. package/src/orderSubscriber/WebsocketSubscription.ts +1 -0
  38. package/tests/amm/test.ts +402 -0
  39. package/tests/auctions/test.ts +66 -0
  40. package/tests/dlob/test.ts +1 -73
@@ -43,8 +43,8 @@ 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
47
- slippageBps = 50, swapMode = 'ExactIn', onlyDirectRoutes = false, }) {
46
+ async getQuote({ inputMint, outputMint, amount, maxAccounts = 50, // 50 is an estimated amount with buffer
47
+ slippageBps = 50, swapMode = 'ExactIn', onlyDirectRoutes = false, excludeDexes = [], }) {
48
48
  const params = new URLSearchParams({
49
49
  inputMint: inputMint.toString(),
50
50
  outputMint: outputMint.toString(),
@@ -53,6 +53,7 @@ class JupiterClient {
53
53
  swapMode,
54
54
  onlyDirectRoutes: onlyDirectRoutes.toString(),
55
55
  maxAccounts: maxAccounts.toString(),
56
+ excludeDexes: excludeDexes.join(','),
56
57
  }).toString();
57
58
  const quote = await (await (0, node_fetch_1.default)(`${this.url}/v6/quote?${params}`)).json();
58
59
  return quote;
@@ -1,7 +1,18 @@
1
- import { Order } from '../types';
1
+ import { Order, PositionDirection } from '../types';
2
2
  import { BN } from '../.';
3
3
  export declare function isAuctionComplete(order: Order, slot: number): boolean;
4
4
  export declare function isFallbackAvailableLiquiditySource(order: Order, minAuctionDuration: number, slot: number): boolean;
5
5
  export declare function getAuctionPrice(order: Order, slot: number, oraclePrice: BN): BN;
6
6
  export declare function getAuctionPriceForFixedAuction(order: Order, slot: number): BN;
7
7
  export declare function getAuctionPriceForOracleOffsetAuction(order: Order, slot: number, oraclePrice: BN): BN;
8
+ export declare function deriveOracleAuctionParams({ direction, oraclePrice, auctionStartPrice, auctionEndPrice, limitPrice, }: {
9
+ direction: PositionDirection;
10
+ oraclePrice: BN;
11
+ auctionStartPrice: BN;
12
+ auctionEndPrice: BN;
13
+ limitPrice: BN;
14
+ }): {
15
+ auctionStartPrice: BN;
16
+ auctionEndPrice: BN;
17
+ oraclePriceOffset: number;
18
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAuctionPriceForOracleOffsetAuction = exports.getAuctionPriceForFixedAuction = exports.getAuctionPrice = exports.isFallbackAvailableLiquiditySource = exports.isAuctionComplete = void 0;
3
+ exports.deriveOracleAuctionParams = exports.getAuctionPriceForOracleOffsetAuction = exports.getAuctionPriceForFixedAuction = exports.getAuctionPrice = exports.isFallbackAvailableLiquiditySource = exports.isAuctionComplete = void 0;
4
4
  const types_1 = require("../types");
5
5
  const _1 = require("../.");
6
6
  function isAuctionComplete(order, slot) {
@@ -89,3 +89,24 @@ function getAuctionPriceForOracleOffsetAuction(order, slot, oraclePrice) {
89
89
  return oraclePrice.add(priceOffset);
90
90
  }
91
91
  exports.getAuctionPriceForOracleOffsetAuction = getAuctionPriceForOracleOffsetAuction;
92
+ function deriveOracleAuctionParams({ direction, oraclePrice, auctionStartPrice, auctionEndPrice, limitPrice, }) {
93
+ let oraclePriceOffset = limitPrice.sub(oraclePrice);
94
+ if (oraclePriceOffset.eq(_1.ZERO)) {
95
+ oraclePriceOffset = (0, types_1.isVariant)(direction, 'long')
96
+ ? auctionEndPrice.sub(oraclePrice).add(_1.ONE)
97
+ : auctionEndPrice.sub(oraclePrice).sub(_1.ONE);
98
+ }
99
+ let oraclePriceOffsetNum;
100
+ try {
101
+ oraclePriceOffsetNum = oraclePriceOffset.toNumber();
102
+ }
103
+ catch (e) {
104
+ oraclePriceOffsetNum = 0;
105
+ }
106
+ return {
107
+ auctionStartPrice: auctionStartPrice.sub(oraclePrice),
108
+ auctionEndPrice: auctionEndPrice.sub(oraclePrice),
109
+ oraclePriceOffset: oraclePriceOffsetNum,
110
+ };
111
+ }
112
+ exports.deriveOracleAuctionParams = deriveOracleAuctionParams;
@@ -130,12 +130,10 @@ exports.calculateNetUserPnlImbalance = calculateNetUserPnlImbalance;
130
130
  function calculateAvailablePerpLiquidity(market, oraclePriceData, dlob, slot) {
131
131
  let [bids, asks] = (0, amm_1.calculateMarketOpenBidAsk)(market.amm.baseAssetReserve, market.amm.minBaseAssetReserve, market.amm.maxBaseAssetReserve, market.amm.orderStepSize);
132
132
  asks = asks.abs();
133
- const bidPrice = calculateBidPrice(market, oraclePriceData);
134
- const askPrice = calculateAskPrice(market, oraclePriceData);
135
- for (const bid of dlob.getMakerLimitBids(market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, askPrice)) {
133
+ for (const bid of dlob.getRestingLimitBids(market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData)) {
136
134
  bids = bids.add(bid.order.baseAssetAmount.sub(bid.order.baseAssetAmountFilled));
137
135
  }
138
- for (const ask of dlob.getMakerLimitAsks(market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, bidPrice)) {
136
+ for (const ask of dlob.getRestingLimitAsks(market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData)) {
139
137
  asks = asks.add(ask.order.baseAssetAmount.sub(ask.order.baseAssetAmountFilled));
140
138
  }
141
139
  return {
@@ -174,7 +174,7 @@ const getJitoSolHistoricalPriceMap = async (timestamps) => {
174
174
  const jitoSolHistoricalPriceInSol = [];
175
175
  for (let i = 0; i < data.data.getStakePoolStats.supply.length; i++) {
176
176
  const priceInSol = data.data.getStakePoolStats.tvl[i].data /
177
- new anchor_1.BN(10).pow(numericConstants_1.NINE) /
177
+ 10 ** 9 /
178
178
  data.data.getStakePoolStats.supply[i].data;
179
179
  jitoSolHistoricalPriceInSol.push({
180
180
  price: priceInSol,
package/lib/math/trade.js CHANGED
@@ -278,9 +278,7 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
278
278
  };
279
279
  }
280
280
  const takerIsLong = (0, types_2.isVariant)(direction, 'long');
281
- const limitOrders = dlob[takerIsLong ? 'getMakerLimitAsks' : 'getMakerLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, takerIsLong
282
- ? (0, market_1.calculateBidPrice)(market, oraclePriceData)
283
- : (0, market_1.calculateAskPrice)(market, oraclePriceData));
281
+ const limitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData);
284
282
  const swapDirection = (0, amm_1.getSwapDirection)(assetType, direction);
285
283
  const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, oraclePriceData);
286
284
  const amm = {
@@ -463,7 +461,7 @@ function calculateEstimatedSpotEntryPrice(assetType, amount, direction, market,
463
461
  }
464
462
  const basePrecision = new anchor_1.BN(Math.pow(10, market.decimals));
465
463
  const takerIsLong = (0, types_2.isVariant)(direction, 'long');
466
- const dlobLimitOrders = dlob[takerIsLong ? 'getMakerLimitAsks' : 'getMakerLimitBids'](market.marketIndex, slot, types_1.MarketType.SPOT, oraclePriceData);
464
+ const dlobLimitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.SPOT, oraclePriceData);
467
465
  const serumLimitOrders = takerIsLong
468
466
  ? serumAsks.getL2(100)
469
467
  : serumBids.getL2(100);
@@ -25,6 +25,8 @@ class WebsocketSubscription {
25
25
  }
26
26
  }
27
27
  async unsubscribe() {
28
+ if (!this.subscriber)
29
+ return;
28
30
  this.subscriber.unsubscribe();
29
31
  }
30
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.40.0-beta.9",
3
+ "version": "2.41.0-beta.1",
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",
@@ -214,3 +214,12 @@ export function getReferrerNamePublicKeySync(
214
214
  programId
215
215
  )[0];
216
216
  }
217
+
218
+ export function getProtocolIfSharesTransferConfigPublicKey(
219
+ programId: PublicKey
220
+ ): PublicKey {
221
+ return PublicKey.findProgramAddressSync(
222
+ [Buffer.from(anchor.utils.bytes.utf8.encode('if_shares_transfer_config'))],
223
+ programId
224
+ )[0];
225
+ }
@@ -25,6 +25,7 @@ import {
25
25
  getSerumOpenOrdersPublicKey,
26
26
  getSerumFulfillmentConfigPublicKey,
27
27
  getPhoenixFulfillmentConfigPublicKey,
28
+ getProtocolIfSharesTransferConfigPublicKey,
28
29
  } from './addresses/pda';
29
30
  import { squareRootBN } from './math/utils';
30
31
  import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
@@ -1516,4 +1517,35 @@ export class AdminClient extends DriftClient {
1516
1517
  }
1517
1518
  );
1518
1519
  }
1520
+
1521
+ public async initializeProtocolIfSharesTransferConfig(): Promise<TransactionSignature> {
1522
+ return await this.program.rpc.initializeProtocolIfSharesTransferConfig({
1523
+ accounts: {
1524
+ admin: this.wallet.publicKey,
1525
+ state: await this.getStatePublicKey(),
1526
+ rent: SYSVAR_RENT_PUBKEY,
1527
+ systemProgram: anchor.web3.SystemProgram.programId,
1528
+ protocolIfSharesTransferConfig:
1529
+ getProtocolIfSharesTransferConfigPublicKey(this.program.programId),
1530
+ },
1531
+ });
1532
+ }
1533
+
1534
+ public async updateProtocolIfSharesTransferConfig(
1535
+ whitelistedSigners?: PublicKey[],
1536
+ maxTransferPerEpoch?: BN
1537
+ ): Promise<TransactionSignature> {
1538
+ return await this.program.rpc.updateProtocolIfSharesTransferConfig(
1539
+ whitelistedSigners || null,
1540
+ maxTransferPerEpoch,
1541
+ {
1542
+ accounts: {
1543
+ admin: this.wallet.publicKey,
1544
+ state: await this.getStatePublicKey(),
1545
+ protocolIfSharesTransferConfig:
1546
+ getProtocolIfSharesTransferConfigPublicKey(this.program.programId),
1547
+ },
1548
+ }
1549
+ );
1550
+ }
1519
1551
  }
@@ -128,7 +128,7 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
128
128
  serumMarket: new PublicKey('B2na8Awyd7cpC59iEU43FagJAPLigr3AP3s38KM982bu'),
129
129
  },
130
130
  {
131
- symbol: 'JitoSOL',
131
+ symbol: 'jitoSOL',
132
132
  marketIndex: 6,
133
133
  oracle: new PublicKey('7yyaeuJ1GGtVBLT2z2xub5ZWYKaNhF28mj1RdV4VDFVk'),
134
134
  oracleSource: OracleSource.PYTH,
package/src/dlob/DLOB.ts CHANGED
@@ -675,7 +675,7 @@ export class DLOB {
675
675
  marketType,
676
676
  oraclePriceData,
677
677
  takingOrderGenerator,
678
- this.getMakerLimitBids.bind(this),
678
+ this.getRestingLimitBids.bind(this),
679
679
  (takerPrice, makerPrice) => {
680
680
  if (isVariant(marketType, 'spot')) {
681
681
  if (takerPrice === undefined) {
@@ -687,8 +687,7 @@ export class DLOB {
687
687
  }
688
688
  }
689
689
  return takerPrice === undefined || takerPrice.lte(makerPrice);
690
- },
691
- fallbackAsk
690
+ }
692
691
  );
693
692
  for (const takingAskCrossingBid of takingAsksCrossingBids) {
694
693
  nodesToFill.push(takingAskCrossingBid);
@@ -732,7 +731,7 @@ export class DLOB {
732
731
  marketType,
733
732
  oraclePriceData,
734
733
  takingOrderGenerator,
735
- this.getMakerLimitAsks.bind(this),
734
+ this.getRestingLimitAsks.bind(this),
736
735
  (takerPrice, makerPrice) => {
737
736
  if (isVariant(marketType, 'spot')) {
738
737
  if (takerPrice === undefined) {
@@ -745,8 +744,7 @@ export class DLOB {
745
744
  }
746
745
 
747
746
  return takerPrice === undefined || takerPrice.gte(makerPrice);
748
- },
749
- fallbackBid
747
+ }
750
748
  );
751
749
 
752
750
  for (const takingBidToFill of takingBidsToFill) {
@@ -790,11 +788,9 @@ export class DLOB {
790
788
  marketIndex: number,
791
789
  slot: number,
792
790
  marketType: MarketType,
793
- oraclePriceData: OraclePriceData,
794
- fallbackPrice?: BN
791
+ oraclePriceData: OraclePriceData
795
792
  ) => Generator<DLOBNode>,
796
- doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean,
797
- fallbackPrice?: BN
793
+ doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean
798
794
  ): NodeToFill[] {
799
795
  const nodesToFill = new Array<NodeToFill>();
800
796
 
@@ -803,8 +799,7 @@ export class DLOB {
803
799
  marketIndex,
804
800
  slot,
805
801
  marketType,
806
- oraclePriceData,
807
- fallbackPrice
802
+ oraclePriceData
808
803
  );
809
804
 
810
805
  for (const makerNode of makerNodeGenerator) {
@@ -1714,8 +1709,6 @@ export class DLOB {
1714
1709
  * @param slot
1715
1710
  * @param oraclePriceData
1716
1711
  * @param depth how many levels of the order book to return
1717
- * @param fallbackAsk best ask for fallback liquidity, only relevant for perps
1718
- * @param fallbackBid best bid for fallback liquidity, only relevant for perps
1719
1712
  * @param fallbackL2Generators L2 generators for fallback liquidity e.g. vAMM {@link getVammL2Generator}, openbook {@link SerumSubscriber}
1720
1713
  */
1721
1714
  public getL2({
@@ -1724,8 +1717,6 @@ export class DLOB {
1724
1717
  slot,
1725
1718
  oraclePriceData,
1726
1719
  depth,
1727
- fallbackAsk,
1728
- fallbackBid,
1729
1720
  fallbackL2Generators = [],
1730
1721
  }: {
1731
1722
  marketIndex: number;
@@ -1733,18 +1724,10 @@ export class DLOB {
1733
1724
  slot: number;
1734
1725
  oraclePriceData: OraclePriceData;
1735
1726
  depth: number;
1736
- fallbackAsk?: BN;
1737
- fallbackBid?: BN;
1738
1727
  fallbackL2Generators?: L2OrderBookGenerator[];
1739
1728
  }): L2OrderBook {
1740
1729
  const makerAskL2LevelGenerator = getL2GeneratorFromDLOBNodes(
1741
- this.getMakerLimitAsks(
1742
- marketIndex,
1743
- slot,
1744
- marketType,
1745
- oraclePriceData,
1746
- fallbackBid
1747
- ),
1730
+ this.getRestingLimitAsks(marketIndex, slot, marketType, oraclePriceData),
1748
1731
  oraclePriceData,
1749
1732
  slot
1750
1733
  );
@@ -1765,13 +1748,7 @@ export class DLOB {
1765
1748
  const asks = createL2Levels(askL2LevelGenerator, depth);
1766
1749
 
1767
1750
  const makerBidGenerator = getL2GeneratorFromDLOBNodes(
1768
- this.getMakerLimitBids(
1769
- marketIndex,
1770
- slot,
1771
- marketType,
1772
- oraclePriceData,
1773
- fallbackAsk
1774
- ),
1751
+ this.getRestingLimitBids(marketIndex, slot, marketType, oraclePriceData),
1775
1752
  oraclePriceData,
1776
1753
  slot
1777
1754
  );
@@ -10,12 +10,12 @@ 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,
16
17
  L3OrderBook,
17
18
  } from './orderBookLevels';
18
- import { calculateAskPrice, calculateBidPrice } from '../math/market';
19
19
 
20
20
  export class DLOBSubscriber {
21
21
  driftClient: DriftClient;
@@ -103,8 +103,6 @@ export class DLOBSubscriber {
103
103
  }
104
104
 
105
105
  let oraclePriceData;
106
- let fallbackBid;
107
- let fallbackAsk;
108
106
  const isPerp = isVariant(marketType, 'perp');
109
107
  if (isPerp) {
110
108
  const perpMarketAccount =
@@ -112,19 +110,24 @@ export class DLOBSubscriber {
112
110
  oraclePriceData = this.driftClient.getOracleDataForPerpMarket(
113
111
  perpMarketAccount.marketIndex
114
112
  );
115
- fallbackBid = calculateBidPrice(perpMarketAccount, oraclePriceData);
116
- fallbackAsk = calculateAskPrice(perpMarketAccount, oraclePriceData);
117
113
  } else {
118
114
  oraclePriceData =
119
115
  this.driftClient.getOracleDataForSpotMarket(marketIndex);
120
116
  }
121
117
 
122
118
  if (isPerp && includeVamm) {
119
+ if (fallbackL2Generators.length > 0) {
120
+ throw new Error(
121
+ 'includeVamm can only be used if fallbackL2Generators is empty'
122
+ );
123
+ }
124
+
123
125
  fallbackL2Generators = [
124
126
  getVammL2Generator({
125
127
  marketAccount: this.driftClient.getPerpMarketAccount(marketIndex),
126
128
  oraclePriceData,
127
129
  numOrders: numVammOrders ?? depth,
130
+ topOfBookQuoteAmounts: DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS,
128
131
  }),
129
132
  ];
130
133
  }
@@ -135,8 +138,6 @@ export class DLOBSubscriber {
135
138
  depth,
136
139
  oraclePriceData,
137
140
  slot: this.slotSource.getSlot(),
138
- fallbackBid,
139
- fallbackAsk,
140
141
  fallbackL2Generators: fallbackL2Generators,
141
142
  });
142
143
  }
@@ -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,9 +48,16 @@ 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
- * @param dlobNodes e.g. {@link DLOB#getMakerLimitAsks} or {@link DLOB#getMakerLimitBids}
60
+ * @param dlobNodes e.g. {@link DLOB#getRestingLimitAsks} or {@link DLOB#getRestingLimitBids}
52
61
  * @param oraclePriceData
53
62
  * @param slot
54
63
  */
@@ -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++;
@@ -618,6 +618,8 @@ export class BigNum {
618
618
  if (!val.replace(BigNum.delim, '')) {
619
619
  return BigNum.from(ZERO, precisionShift);
620
620
  }
621
+ if (val.includes('e'))
622
+ val = (+val).toFixed(precisionShift?.toNumber() ?? 9); // prevent small numbers e.g. 3.1e-8, use assume max precision 9 as default
621
623
 
622
624
  const sides = val.split(BigNum.delim);
623
625
  const rightSide = sides[1];