@drift-labs/sdk 0.2.0-master.24 → 0.2.0-master.25

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.
@@ -183,7 +183,9 @@ export class ClearingHouseUser {
183
183
 
184
184
  /**
185
185
  * calculates the market position if the lp position was settled
186
- * @returns : userPosition
186
+ * @returns : the settled userPosition
187
+ * @returns : the dust base asset amount (ie, < stepsize)
188
+ * @returns : pnl from settle
187
189
  */
188
190
  public getSettledLPPosition(marketIndex: BN): [UserPosition, BN, BN] {
189
191
  const _position = this.getUserPosition(marketIndex);
@@ -224,9 +226,6 @@ export class ClearingHouseUser {
224
226
 
225
227
  const reaminderPerLP = remainderBaa.mul(AMM_RESERVE_PRECISION).div(nShares);
226
228
 
227
- position.baseAssetAmount = position.baseAssetAmount.add(standardizedBaa);
228
- position.quoteAssetAmount = position.quoteAssetAmount.add(deltaQaa);
229
-
230
229
  position.lastNetBaseAssetAmountPerLp =
231
230
  market.amm.marketPositionPerLp.baseAssetAmount.sub(reaminderPerLP);
232
231
 
@@ -254,7 +253,7 @@ export class ClearingHouseUser {
254
253
  .mul(deltaBaa.abs())
255
254
  .div(position.baseAssetAmount.abs())
256
255
  );
257
- pnl = position.quoteEntryAmount.sub(newQuoteEntry);
256
+ pnl = position.quoteEntryAmount.sub(newQuoteEntry).add(deltaQaa);
258
257
  } else {
259
258
  newQuoteEntry = deltaQaa.sub(
260
259
  deltaQaa.mul(position.baseAssetAmount.abs()).div(deltaBaa.abs())
@@ -262,6 +261,8 @@ export class ClearingHouseUser {
262
261
  pnl = position.quoteEntryAmount.add(deltaQaa.sub(newQuoteEntry));
263
262
  }
264
263
  position.quoteEntryAmount = newQuoteEntry;
264
+ position.baseAssetAmount = position.baseAssetAmount.add(standardizedBaa);
265
+ position.quoteAssetAmount = position.quoteAssetAmount.add(deltaQaa);
265
266
 
266
267
  if (position.baseAssetAmount.gt(ZERO)) {
267
268
  position.lastCumulativeFundingRate = market.amm.cumulativeFundingRateLong;
package/src/config.ts CHANGED
@@ -28,7 +28,7 @@ export const configs: { [key in DriftEnv]: DriftConfig } = {
28
28
  devnet: {
29
29
  ENV: 'devnet',
30
30
  PYTH_ORACLE_MAPPING_ADDRESS: 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2',
31
- CLEARING_HOUSE_PROGRAM_ID: 'HbUKwqFPjz9r7ZtT416AU8aaecDQMKKiDCTS3AdXnS6c',
31
+ CLEARING_HOUSE_PROGRAM_ID: '3v1iEjbSSLSSYyt1pmx4UB5rqJGurmz71RibXF7X6UF3',
32
32
  USDC_MINT_ADDRESS: '8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2',
33
33
  MARKETS: DevnetMarkets,
34
34
  BANKS: DevnetBanks,
@@ -25,6 +25,8 @@ export class EventSubscriber {
25
25
  private awaitTxResolver = new Map<string, () => void>();
26
26
  private logProvider: LogProvider;
27
27
  public eventEmitter: StrictEventEmitter<EventEmitter, EventSubscriberEvents>;
28
+ private lastSeenSlot: number;
29
+ public lastSeenTxSig: string;
28
30
 
29
31
  public constructor(
30
32
  private connection: Connection,
@@ -62,19 +64,22 @@ export class EventSubscriber {
62
64
  }
63
65
  }
64
66
 
65
- public subscribe(): boolean {
66
- if (this.logProvider.isSubscribed()) {
67
- return true;
68
- }
67
+ public async subscribe(): Promise<boolean> {
68
+ try {
69
+ if (this.logProvider.isSubscribed()) {
70
+ return true;
71
+ }
69
72
 
70
- this.fetchPreviousTx().catch((e) => {
73
+ this.logProvider.subscribe((txSig, slot, logs) => {
74
+ this.handleTxLogs(txSig, slot, logs);
75
+ }, true);
76
+
77
+ return true;
78
+ } catch (e) {
71
79
  console.error('Error fetching previous txs in event subscriber');
72
80
  console.error(e);
73
- });
74
-
75
- return this.logProvider.subscribe((txSig, slot, logs) => {
76
- this.handleTxLogs(txSig, slot, logs);
77
- });
81
+ return false;
82
+ }
78
83
  }
79
84
 
80
85
  private handleTxLogs(
@@ -98,11 +103,14 @@ export class EventSubscriber {
98
103
  this.awaitTxResolver.delete(txSig);
99
104
  }
100
105
 
106
+ if (slot > this.lastSeenSlot) {
107
+ this.lastSeenTxSig = txSig;
108
+ }
101
109
  this.txEventCache.add(txSig, wrappedEvents);
102
110
  }
103
111
 
104
- private async fetchPreviousTx(): Promise<void> {
105
- if (!this.options.untilTx) {
112
+ public async fetchPreviousTx(fetchMax?: boolean): Promise<void> {
113
+ if (!this.options.untilTx && !fetchMax) {
106
114
  return;
107
115
  }
108
116
 
@@ -1,14 +1,18 @@
1
+ import { Program } from '@project-serum/anchor';
1
2
  import {
2
3
  Connection,
3
4
  Finality,
4
5
  PublicKey,
5
6
  TransactionSignature,
6
7
  } from '@solana/web3.js';
8
+ import { WrappedEvents } from './types';
7
9
 
8
10
  type Log = { txSig: TransactionSignature; slot: number; logs: string[] };
9
11
  type FetchLogsResponse = {
10
12
  earliestTx: string;
11
13
  mostRecentTx: string;
14
+ earliestSlot: number;
15
+ mostRecentSlot: number;
12
16
  transactionLogs: Log[];
13
17
  };
14
18
 
@@ -17,19 +21,21 @@ export async function fetchLogs(
17
21
  programId: PublicKey,
18
22
  finality: Finality,
19
23
  beforeTx?: TransactionSignature,
20
- untilTx?: TransactionSignature
21
- ): Promise<FetchLogsResponse | undefined> {
24
+ untilTx?: TransactionSignature,
25
+ limit?: number
26
+ ): Promise<FetchLogsResponse> {
22
27
  const signatures = await connection.getSignaturesForAddress(
23
28
  programId,
24
29
  {
25
30
  before: beforeTx,
26
31
  until: untilTx,
32
+ limit,
27
33
  },
28
34
  finality
29
35
  );
30
36
 
31
37
  const sortedSignatures = signatures.sort((a, b) =>
32
- a.slot < b.slot ? -1 : 1
38
+ a.slot === b.slot ? 0 : a.slot < b.slot ? -1 : 1
33
39
  );
34
40
 
35
41
  const filteredSignatures = sortedSignatures.filter(
@@ -61,14 +67,15 @@ export async function fetchLogs(
61
67
  )
62
68
  ).flat();
63
69
 
64
- const earliestTx = filteredSignatures[0].signature;
65
- const mostRecentTx =
66
- filteredSignatures[filteredSignatures.length - 1].signature;
70
+ const earliest = filteredSignatures[0];
71
+ const mostRecent = filteredSignatures[filteredSignatures.length - 1];
67
72
 
68
73
  return {
69
74
  transactionLogs: transactionLogs,
70
- earliestTx: earliestTx,
71
- mostRecentTx: mostRecentTx,
75
+ earliestTx: earliest.signature,
76
+ mostRecentTx: mostRecent.signature,
77
+ earliestSlot: earliest.slot,
78
+ mostRecentSlot: mostRecent.slot,
72
79
  };
73
80
  }
74
81
 
@@ -78,3 +85,23 @@ function chunk<T>(array: readonly T[], size: number): T[][] {
78
85
  .map((_, index) => index * size)
79
86
  .map((begin) => array.slice(begin, begin + size));
80
87
  }
88
+
89
+ export class LogParser {
90
+ private program: Program;
91
+
92
+ constructor(program: Program) {
93
+ this.program = program;
94
+ }
95
+
96
+ public parseEventsFromLogs(event: Log): WrappedEvents {
97
+ const records: WrappedEvents = [];
98
+ // @ts-ignore
99
+ this.program._events._eventParser.parseLogs(event.logs, (eventLog) => {
100
+ eventLog.data.txSig = event.txSig;
101
+ eventLog.data.slot = event.slot;
102
+ eventLog.data.eventType = eventLog.name;
103
+ records.push(eventLog.data);
104
+ });
105
+ return records;
106
+ }
107
+ }
@@ -13,6 +13,7 @@ export class PollingLogProvider implements LogProvider {
13
13
  private intervalId: NodeJS.Timer;
14
14
  private mostRecentSeenTx?: TransactionSignature;
15
15
  private mutex: number;
16
+ private firstFetch = true;
16
17
 
17
18
  public constructor(
18
19
  private connection: Connection,
@@ -23,7 +24,10 @@ export class PollingLogProvider implements LogProvider {
23
24
  this.finality = commitment === 'finalized' ? 'finalized' : 'confirmed';
24
25
  }
25
26
 
26
- public subscribe(callback: logProviderCallback): boolean {
27
+ public subscribe(
28
+ callback: logProviderCallback,
29
+ skipHistory?: boolean
30
+ ): boolean {
27
31
  if (this.intervalId) {
28
32
  return true;
29
33
  }
@@ -40,9 +44,13 @@ export class PollingLogProvider implements LogProvider {
40
44
  this.programId,
41
45
  this.finality,
42
46
  undefined,
43
- this.mostRecentSeenTx
47
+ this.mostRecentSeenTx,
48
+ // If skipping history, only fetch one log back, not the maximum amount available
49
+ skipHistory && this.firstFetch ? 1 : undefined
44
50
  );
45
51
 
52
+ this.firstFetch = false;
53
+
46
54
  if (response === undefined) {
47
55
  return;
48
56
  }
@@ -8,6 +8,7 @@ import {
8
8
  OrderActionRecord,
9
9
  OrderRecord,
10
10
  SettlePnlRecord,
11
+ LPRecord,
11
12
  } from '../index';
12
13
 
13
14
  export type EventSubscriptionOptions = {
@@ -33,6 +34,7 @@ export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
33
34
  'FundingRateRecord',
34
35
  'NewUserRecord',
35
36
  'SettlePnlRecord',
37
+ 'LPRecord',
36
38
  ],
37
39
  maxEventsPerType: 4096,
38
40
  orderBy: 'blockchain',
@@ -68,6 +70,7 @@ export type EventMap = {
68
70
  OrderActionRecord: Event<OrderActionRecord>;
69
71
  SettlePnlRecord: Event<SettlePnlRecord>;
70
72
  NewUserRecord: Event<NewUserRecord>;
73
+ LPRecord: Event<LPRecord>;
71
74
  };
72
75
 
73
76
  export type EventType = keyof EventMap;
@@ -89,7 +92,7 @@ export type logProviderCallback = (
89
92
 
90
93
  export interface LogProvider {
91
94
  isSubscribed(): boolean;
92
- subscribe(callback: logProviderCallback): boolean;
95
+ subscribe(callback: logProviderCallback, skipHistory?: boolean): boolean;
93
96
  unsubscribe(): Promise<boolean>;
94
97
  }
95
98
 
@@ -10,6 +10,8 @@ import {
10
10
  PositionDirection,
11
11
  convertToNumber,
12
12
  calculateTradeSlippage,
13
+ BulkAccountLoader,
14
+ Markets,
13
15
  MARK_PRICE_PRECISION,
14
16
  QUOTE_PRECISION,
15
17
  } from '..';
@@ -27,9 +29,11 @@ export const getTokenAddress = (
27
29
  );
28
30
  };
29
31
 
32
+ const cluster = 'devnet';
33
+
30
34
  const main = async () => {
31
35
  // Initialize Drift SDK
32
- const sdkConfig = initialize({ env: 'devnet' });
36
+ const sdkConfig = initialize({ env: cluster });
33
37
 
34
38
  // Set up the Wallet and Provider
35
39
  const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
@@ -63,10 +67,21 @@ const main = async () => {
63
67
  const clearingHousePublicKey = new PublicKey(
64
68
  sdkConfig.CLEARING_HOUSE_PROGRAM_ID
65
69
  );
70
+ const bulkAccountLoader = new BulkAccountLoader(
71
+ connection,
72
+ 'confirmed',
73
+ 1000
74
+ );
66
75
  const clearingHouse = new ClearingHouse({
67
76
  connection,
68
77
  wallet: provider.wallet,
69
78
  programID: clearingHousePublicKey,
79
+ marketIndexes: Markets[cluster].map((market) => market.marketIndex),
80
+ bankIndexes: Banks[cluster].map((bank) => bank.bankIndex),
81
+ accountSubscription: {
82
+ type: 'polling',
83
+ accountLoader: bulkAccountLoader,
84
+ },
70
85
  });
71
86
  await clearingHouse.subscribe();
72
87
 
@@ -74,6 +89,10 @@ const main = async () => {
74
89
  const user = new ClearingHouseUser({
75
90
  clearingHouse,
76
91
  userAccountPublicKey: await clearingHouse.getUserAccountPublicKey(),
92
+ accountSubscription: {
93
+ type: 'polling',
94
+ accountLoader: bulkAccountLoader,
95
+ },
77
96
  });
78
97
 
79
98
  //// Check if clearing house account exists for the current wallet
@@ -26,7 +26,7 @@
26
26
  "isSigner": false
27
27
  },
28
28
  {
29
- "name": "insuranceVaultAuthority",
29
+ "name": "clearingHouseSigner",
30
30
  "isMut": false,
31
31
  "isSigner": false
32
32
  },
@@ -71,18 +71,13 @@
71
71
  "isMut": true,
72
72
  "isSigner": false
73
73
  },
74
- {
75
- "name": "bankVaultAuthority",
76
- "isMut": false,
77
- "isSigner": false
78
- },
79
74
  {
80
75
  "name": "insuranceFundVault",
81
76
  "isMut": true,
82
77
  "isSigner": false
83
78
  },
84
79
  {
85
- "name": "insuranceFundVaultAuthority",
80
+ "name": "clearingHouseSigner",
86
81
  "isMut": false,
87
82
  "isSigner": false
88
83
  },
@@ -316,8 +311,8 @@
316
311
  "isSigner": false
317
312
  },
318
313
  {
319
- "name": "bankVaultAuthority",
320
- "isMut": true,
314
+ "name": "clearingHouseSigner",
315
+ "isMut": false,
321
316
  "isSigner": false
322
317
  },
323
318
  {
@@ -974,8 +969,8 @@
974
969
  "isSigner": false
975
970
  },
976
971
  {
977
- "name": "insuranceFundVaultAuthority",
978
- "isMut": true,
972
+ "name": "clearingHouseSigner",
973
+ "isMut": false,
979
974
  "isSigner": false
980
975
  },
981
976
  {
@@ -1029,8 +1024,8 @@
1029
1024
  "isSigner": false
1030
1025
  },
1031
1026
  {
1032
- "name": "insuranceFundVaultAuthority",
1033
- "isMut": true,
1027
+ "name": "clearingHouseSigner",
1028
+ "isMut": false,
1034
1029
  "isSigner": false
1035
1030
  },
1036
1031
  {
@@ -1100,8 +1095,8 @@
1100
1095
  "isSigner": false
1101
1096
  },
1102
1097
  {
1103
- "name": "bankVaultAuthority",
1104
- "isMut": true,
1098
+ "name": "clearingHouseSigner",
1099
+ "isMut": false,
1105
1100
  "isSigner": false
1106
1101
  },
1107
1102
  {
@@ -1146,7 +1141,7 @@
1146
1141
  "isSigner": false
1147
1142
  },
1148
1143
  {
1149
- "name": "insuranceVaultAuthority",
1144
+ "name": "clearingHouseSigner",
1150
1145
  "isMut": false,
1151
1146
  "isSigner": false
1152
1147
  },
@@ -1192,7 +1187,7 @@
1192
1187
  "isSigner": false
1193
1188
  },
1194
1189
  {
1195
- "name": "insuranceVaultAuthority",
1190
+ "name": "clearingHouseSigner",
1196
1191
  "isMut": false,
1197
1192
  "isSigner": false
1198
1193
  },
@@ -2414,8 +2409,8 @@
2414
2409
  "isSigner": false
2415
2410
  },
2416
2411
  {
2417
- "name": "bankVaultAuthority",
2418
- "isMut": true,
2412
+ "name": "clearingHouseSigner",
2413
+ "isMut": false,
2419
2414
  "isSigner": false
2420
2415
  },
2421
2416
  {
@@ -2565,6 +2560,11 @@
2565
2560
  {
2566
2561
  "name": "removeInsuranceFundStake",
2567
2562
  "accounts": [
2563
+ {
2564
+ "name": "state",
2565
+ "isMut": false,
2566
+ "isSigner": false
2567
+ },
2568
2568
  {
2569
2569
  "name": "bank",
2570
2570
  "isMut": false,
@@ -2591,8 +2591,8 @@
2591
2591
  "isSigner": false
2592
2592
  },
2593
2593
  {
2594
- "name": "insuranceFundVaultAuthority",
2595
- "isMut": true,
2594
+ "name": "clearingHouseSigner",
2595
+ "isMut": false,
2596
2596
  "isSigner": false
2597
2597
  },
2598
2598
  {
@@ -2646,26 +2646,10 @@
2646
2646
  "name": "vault",
2647
2647
  "type": "publicKey"
2648
2648
  },
2649
- {
2650
- "name": "vaultAuthority",
2651
- "type": "publicKey"
2652
- },
2653
- {
2654
- "name": "vaultAuthorityNonce",
2655
- "type": "u8"
2656
- },
2657
2649
  {
2658
2650
  "name": "insuranceFundVault",
2659
2651
  "type": "publicKey"
2660
2652
  },
2661
- {
2662
- "name": "insuranceFundVaultAuthority",
2663
- "type": "publicKey"
2664
- },
2665
- {
2666
- "name": "insuranceFundVaultAuthorityNonce",
2667
- "type": "u8"
2668
- },
2669
2653
  {
2670
2654
  "name": "revenuePool",
2671
2655
  "type": {
@@ -2976,14 +2960,6 @@
2976
2960
  "name": "insuranceVault",
2977
2961
  "type": "publicKey"
2978
2962
  },
2979
- {
2980
- "name": "insuranceVaultAuthority",
2981
- "type": "publicKey"
2982
- },
2983
- {
2984
- "name": "insuranceVaultNonce",
2985
- "type": "u8"
2986
- },
2987
2963
  {
2988
2964
  "name": "marginRatioInitial",
2989
2965
  "type": "u128"
@@ -3072,6 +3048,14 @@
3072
3048
  "name": "liquidationMarginBufferRatio",
3073
3049
  "type": "u8"
3074
3050
  },
3051
+ {
3052
+ "name": "signer",
3053
+ "type": "publicKey"
3054
+ },
3055
+ {
3056
+ "name": "signerNonce",
3057
+ "type": "u8"
3058
+ },
3075
3059
  {
3076
3060
  "name": "padding0",
3077
3061
  "type": "u128"
@@ -3352,6 +3336,10 @@
3352
3336
  "name": "quoteAssetAmount",
3353
3337
  "type": "i128"
3354
3338
  },
3339
+ {
3340
+ "name": "lpShares",
3341
+ "type": "u128"
3342
+ },
3355
3343
  {
3356
3344
  "name": "userPnl",
3357
3345
  "type": "i128"
@@ -4478,6 +4466,23 @@
4478
4466
  ]
4479
4467
  }
4480
4468
  },
4469
+ {
4470
+ "name": "LPAction",
4471
+ "type": {
4472
+ "kind": "enum",
4473
+ "variants": [
4474
+ {
4475
+ "name": "AddLiquidity"
4476
+ },
4477
+ {
4478
+ "name": "RemoveLiquidity"
4479
+ },
4480
+ {
4481
+ "name": "SettleLiquidity"
4482
+ }
4483
+ ]
4484
+ }
4485
+ },
4481
4486
  {
4482
4487
  "name": "LiquidationType",
4483
4488
  "type": {
@@ -5173,6 +5178,53 @@
5173
5178
  }
5174
5179
  ]
5175
5180
  },
5181
+ {
5182
+ "name": "LPRecord",
5183
+ "fields": [
5184
+ {
5185
+ "name": "ts",
5186
+ "type": "i64",
5187
+ "index": false
5188
+ },
5189
+ {
5190
+ "name": "user",
5191
+ "type": "publicKey",
5192
+ "index": false
5193
+ },
5194
+ {
5195
+ "name": "action",
5196
+ "type": {
5197
+ "defined": "LPAction"
5198
+ },
5199
+ "index": false
5200
+ },
5201
+ {
5202
+ "name": "nShares",
5203
+ "type": "u128",
5204
+ "index": false
5205
+ },
5206
+ {
5207
+ "name": "marketIndex",
5208
+ "type": "u64",
5209
+ "index": false
5210
+ },
5211
+ {
5212
+ "name": "deltaBaseAssetAmount",
5213
+ "type": "i128",
5214
+ "index": false
5215
+ },
5216
+ {
5217
+ "name": "deltaQuoteAssetAmount",
5218
+ "type": "i128",
5219
+ "index": false
5220
+ },
5221
+ {
5222
+ "name": "pnl",
5223
+ "type": "i128",
5224
+ "index": false
5225
+ }
5226
+ ]
5227
+ },
5176
5228
  {
5177
5229
  "name": "LiquidationRecord",
5178
5230
  "fields": [
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ export * from './factory/oracleClient';
30
30
  export * from './factory/bigNum';
31
31
  export * from './events/types';
32
32
  export * from './events/eventSubscriber';
33
+ export * from './events/fetchLogs';
33
34
  export * from './math/auction';
34
35
  export * from './math/conversion';
35
36
  export * from './math/funding';
package/src/math/amm.ts CHANGED
@@ -308,6 +308,51 @@ export function calculateAmmReservesAfterSwap(
308
308
  return [newQuoteAssetReserve, newBaseAssetReserve];
309
309
  }
310
310
 
311
+ export function calculateMarketOpenBidAsk(
312
+ baseAssetReserve: BN,
313
+ minBaseAssetReserve: BN,
314
+ maxBaseAssetReserve: BN
315
+ ): [BN, BN] {
316
+ // open orders
317
+ let openAsks;
318
+ if (maxBaseAssetReserve > baseAssetReserve) {
319
+ openAsks = maxBaseAssetReserve.sub(baseAssetReserve).mul(new BN(-1));
320
+ } else {
321
+ openAsks = ZERO;
322
+ }
323
+
324
+ let openBids;
325
+ if (minBaseAssetReserve < baseAssetReserve) {
326
+ openBids = baseAssetReserve.sub(minBaseAssetReserve);
327
+ } else {
328
+ openBids = ZERO;
329
+ }
330
+ return [openBids, openAsks];
331
+ }
332
+
333
+ export function calculateInventoryScale(
334
+ netBaseAssetAmount: BN,
335
+ baseAssetReserve: BN,
336
+ minBaseAssetReserve: BN,
337
+ maxBaseAssetReserve: BN
338
+ ): number {
339
+ // inventory skew
340
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
341
+ baseAssetReserve,
342
+ minBaseAssetReserve,
343
+ maxBaseAssetReserve
344
+ );
345
+
346
+ const totalLiquidity = BN.max(openBids.abs().add(openAsks.abs()), new BN(1));
347
+ const inventoryScale =
348
+ BN.min(netBaseAssetAmount.abs(), totalLiquidity)
349
+ .mul(BID_ASK_SPREAD_PRECISION.mul(new BN(5)))
350
+ .div(totalLiquidity)
351
+ .toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
352
+
353
+ return inventoryScale;
354
+ }
355
+
311
356
  export function calculateEffectiveLeverage(
312
357
  baseSpread: number,
313
358
  quoteAssetReserve: BN,
@@ -353,7 +398,10 @@ export function calculateSpreadBN(
353
398
  pegMultiplier: BN,
354
399
  netBaseAssetAmount: BN,
355
400
  markPrice: BN,
356
- totalFeeMinusDistributions: BN
401
+ totalFeeMinusDistributions: BN,
402
+ baseAssetReserve: BN,
403
+ minBaseAssetReserve: BN,
404
+ maxBaseAssetReserve: BN
357
405
  ): [number, number] {
358
406
  let longSpread = baseSpread / 2;
359
407
  let shortSpread = baseSpread / 2;
@@ -374,6 +422,20 @@ export function calculateSpreadBN(
374
422
 
375
423
  const MAX_INVENTORY_SKEW = 5;
376
424
 
425
+ const inventoryScale = calculateInventoryScale(
426
+ netBaseAssetAmount,
427
+ baseAssetReserve,
428
+ minBaseAssetReserve,
429
+ maxBaseAssetReserve
430
+ );
431
+ const inventorySpreadScale = Math.min(MAX_INVENTORY_SKEW, 1 + inventoryScale);
432
+
433
+ if (netBaseAssetAmount.gt(ZERO)) {
434
+ longSpread *= inventorySpreadScale;
435
+ } else if (netBaseAssetAmount.lt(ZERO)) {
436
+ shortSpread *= inventorySpreadScale;
437
+ }
438
+
377
439
  const effectiveLeverage = calculateEffectiveLeverage(
378
440
  baseSpread,
379
441
  quoteAssetReserve,
@@ -447,7 +509,10 @@ export function calculateSpread(
447
509
  amm.pegMultiplier,
448
510
  amm.netBaseAssetAmount,
449
511
  markPrice,
450
- amm.totalFeeMinusDistributions
512
+ amm.totalFeeMinusDistributions,
513
+ amm.baseAssetReserve,
514
+ amm.minBaseAssetReserve,
515
+ amm.maxBaseAssetReserve
451
516
  );
452
517
 
453
518
  let spread: number;