@drift-labs/sdk 2.37.1-beta.8 → 2.38.0

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.37.1-beta.8
1
+ 2.37.1-beta.13
@@ -168,7 +168,18 @@ class BulkAccountLoader {
168
168
  }
169
169
  handleAccountCallbacks(accountToLoad, buffer, slot) {
170
170
  for (const [_, callback] of accountToLoad.callbacks) {
171
- callback(buffer, slot);
171
+ try {
172
+ callback(buffer, slot);
173
+ }
174
+ catch (e) {
175
+ console.log('Bulk account load: error in account callback');
176
+ console.log('accounto to load', accountToLoad.publicKey.toString());
177
+ console.log('buffer', buffer.toString('base64'));
178
+ for (const callback of accountToLoad.callbacks.values()) {
179
+ console.log('account to load cb', callback);
180
+ }
181
+ throw e;
182
+ }
172
183
  }
173
184
  }
174
185
  getBufferAndSlot(publicKey) {
@@ -26,6 +26,7 @@ export declare class AdminClient extends DriftClient {
26
26
  updateAmmJitIntensity(perpMarketIndex: number, ammJitIntensity: number): Promise<TransactionSignature>;
27
27
  updatePerpMarketName(perpMarketIndex: number, name: string): Promise<TransactionSignature>;
28
28
  updateSpotMarketName(spotMarketIndex: number, name: string): Promise<TransactionSignature>;
29
+ updatePerpMarketPerLpBase(perpMarketIndex: number, perLpBase: number): Promise<TransactionSignature>;
29
30
  updatePerpMarketMaxSpread(perpMarketIndex: number, maxSpread: number): Promise<TransactionSignature>;
30
31
  updatePerpFeeStructure(feeStructure: FeeStructure): Promise<TransactionSignature>;
31
32
  updateSpotFeeStructure(feeStructure: FeeStructure): Promise<TransactionSignature>;
@@ -365,6 +365,16 @@ class AdminClient extends driftClient_1.DriftClient {
365
365
  },
366
366
  });
367
367
  }
368
+ async updatePerpMarketPerLpBase(perpMarketIndex, perLpBase) {
369
+ const perpMarketPublicKey = await (0, pda_1.getPerpMarketPublicKey)(this.program.programId, perpMarketIndex);
370
+ return await this.program.rpc.updatePerpMarketPerLpBase(perLpBase, {
371
+ accounts: {
372
+ admin: this.wallet.publicKey,
373
+ state: await this.getStatePublicKey(),
374
+ perpMarket: perpMarketPublicKey,
375
+ },
376
+ });
377
+ }
368
378
  async updatePerpMarketMaxSpread(perpMarketIndex, maxSpread) {
369
379
  const perpMarketPublicKey = await (0, pda_1.getPerpMarketPublicKey)(this.program.programId, perpMarketIndex);
370
380
  return await this.program.rpc.updatePerpMarketMaxSpread(maxSpread, {
@@ -30,7 +30,7 @@ class EventSubscriber {
30
30
  this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(this.connection, this.address, this.options.commitment);
31
31
  }
32
32
  else {
33
- this.logProvider = new pollingLogProvider_1.PollingLogProvider(this.connection, this.address, options.commitment, this.options.logProviderConfig.frequency);
33
+ this.logProvider = new pollingLogProvider_1.PollingLogProvider(this.connection, this.address, options.commitment, this.options.logProviderConfig.frequency, this.options.logProviderConfig.batchSize);
34
34
  }
35
35
  }
36
36
  async subscribe() {
@@ -14,7 +14,7 @@ type FetchLogsResponse = {
14
14
  transactionLogs: Log[];
15
15
  mostRecentBlockTime: number | undefined;
16
16
  };
17
- export declare function fetchLogs(connection: Connection, address: PublicKey, finality: Finality, beforeTx?: TransactionSignature, untilTx?: TransactionSignature, limit?: number): Promise<FetchLogsResponse>;
17
+ export declare function fetchLogs(connection: Connection, address: PublicKey, finality: Finality, beforeTx?: TransactionSignature, untilTx?: TransactionSignature, limit?: number, batchSize?: number): Promise<FetchLogsResponse>;
18
18
  export declare function fetchTransactionLogs(connection: Connection, signatures: TransactionSignature[], finality: Finality): Promise<Log[]>;
19
19
  export declare class LogParser {
20
20
  private program;
@@ -9,7 +9,7 @@ function mapTransactionResponseToLog(transaction) {
9
9
  logs: transaction.meta.logMessages,
10
10
  };
11
11
  }
12
- async function fetchLogs(connection, address, finality, beforeTx, untilTx, limit) {
12
+ async function fetchLogs(connection, address, finality, beforeTx, untilTx, limit, batchSize = 25) {
13
13
  const signatures = await connection.getSignaturesForAddress(address, {
14
14
  before: beforeTx,
15
15
  until: untilTx,
@@ -20,7 +20,7 @@ async function fetchLogs(connection, address, finality, beforeTx, untilTx, limit
20
20
  if (filteredSignatures.length === 0) {
21
21
  return undefined;
22
22
  }
23
- const chunkedSignatures = chunk(filteredSignatures, 100);
23
+ const chunkedSignatures = chunk(filteredSignatures, batchSize);
24
24
  const transactionLogs = (await Promise.all(chunkedSignatures.map(async (chunk) => {
25
25
  return await fetchTransactionLogs(connection, chunk.map((confirmedSignature) => confirmedSignature.signature), finality);
26
26
  }))).flat();
@@ -4,12 +4,13 @@ export declare class PollingLogProvider implements LogProvider {
4
4
  private connection;
5
5
  private address;
6
6
  private frequency;
7
+ private batchSize?;
7
8
  private finality;
8
9
  private intervalId;
9
10
  private mostRecentSeenTx?;
10
11
  private mutex;
11
12
  private firstFetch;
12
- constructor(connection: Connection, address: PublicKey, commitment: Commitment, frequency?: number);
13
+ constructor(connection: Connection, address: PublicKey, commitment: Commitment, frequency?: number, batchSize?: number);
13
14
  subscribe(callback: logProviderCallback, skipHistory?: boolean): boolean;
14
15
  isSubscribed(): boolean;
15
16
  unsubscribe(): Promise<boolean>;
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PollingLogProvider = void 0;
4
4
  const fetchLogs_1 = require("./fetchLogs");
5
5
  class PollingLogProvider {
6
- constructor(connection, address, commitment, frequency = 15 * 1000) {
6
+ constructor(connection, address, commitment, frequency = 15 * 1000, batchSize) {
7
7
  this.connection = connection;
8
8
  this.address = address;
9
9
  this.frequency = frequency;
10
+ this.batchSize = batchSize;
10
11
  this.firstFetch = true;
11
12
  this.finality = commitment === 'finalized' ? 'finalized' : 'confirmed';
12
13
  }
@@ -22,7 +23,7 @@ class PollingLogProvider {
22
23
  try {
23
24
  const response = await (0, fetchLogs_1.fetchLogs)(this.connection, this.address, this.finality, undefined, this.mostRecentSeenTx,
24
25
  // If skipping history, only fetch one log back, not the maximum amount available
25
- skipHistory && this.firstFetch ? 1 : undefined);
26
+ skipHistory && this.firstFetch ? 1 : undefined, this.batchSize);
26
27
  if (response === undefined) {
27
28
  return;
28
29
  }
@@ -57,5 +57,6 @@ export type WebSocketLogProviderConfig = {
57
57
  export type PollingLogProviderConfig = {
58
58
  type: 'polling';
59
59
  frequency: number;
60
+ batchSize?: number;
60
61
  };
61
62
  export type LogProviderConfig = WebSocketLogProviderConfig | PollingLogProviderConfig;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.37.0",
2
+ "version": "2.38.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -3882,6 +3882,32 @@
3882
3882
  }
3883
3883
  ]
3884
3884
  },
3885
+ {
3886
+ "name": "updatePerpMarketPerLpBase",
3887
+ "accounts": [
3888
+ {
3889
+ "name": "admin",
3890
+ "isMut": false,
3891
+ "isSigner": true
3892
+ },
3893
+ {
3894
+ "name": "state",
3895
+ "isMut": false,
3896
+ "isSigner": false
3897
+ },
3898
+ {
3899
+ "name": "perpMarket",
3900
+ "isMut": true,
3901
+ "isSigner": false
3902
+ }
3903
+ ],
3904
+ "args": [
3905
+ {
3906
+ "name": "perLpBase",
3907
+ "type": "i8"
3908
+ }
3909
+ ]
3910
+ },
3885
3911
  {
3886
3912
  "name": "updateLpCooldownTime",
3887
3913
  "accounts": [
@@ -6978,9 +7004,20 @@
6978
7004
  ],
6979
7005
  "type": "i32"
6980
7006
  },
7007
+ {
7008
+ "name": "perLpBase",
7009
+ "docs": [
7010
+ "expo for unit of per_lp, base 10 (if per_lp_base=X, then per_lp unit is 10^X)"
7011
+ ],
7012
+ "type": "i8"
7013
+ },
6981
7014
  {
6982
7015
  "name": "padding1",
6983
- "type": "u32"
7016
+ "type": "u8"
7017
+ },
7018
+ {
7019
+ "name": "padding2",
7020
+ "type": "u16"
6984
7021
  },
6985
7022
  {
6986
7023
  "name": "totalFeeEarnedPerLp",
@@ -7447,13 +7484,8 @@
7447
7484
  "type": "u8"
7448
7485
  },
7449
7486
  {
7450
- "name": "padding",
7451
- "type": {
7452
- "array": [
7453
- "u8",
7454
- 1
7455
- ]
7456
- }
7487
+ "name": "perLpBase",
7488
+ "type": "i8"
7457
7489
  }
7458
7490
  ]
7459
7491
  }
package/lib/types.d.ts CHANGED
@@ -821,6 +821,7 @@ export type AMM = {
821
821
  bidQuoteAssetReserve: BN;
822
822
  askBaseAssetReserve: BN;
823
823
  askQuoteAssetReserve: BN;
824
+ perLpBase: number;
824
825
  };
825
826
  export type PerpPosition = {
826
827
  baseAssetAmount: BN;
@@ -837,6 +838,7 @@ export type PerpPosition = {
837
838
  remainderBaseAssetAmount: number;
838
839
  lastBaseAssetAmountPerLp: BN;
839
840
  lastQuoteAssetAmountPerLp: BN;
841
+ perLpBase: number;
840
842
  };
841
843
  export type UserStatsAccount = {
842
844
  numberOfSubAccounts: number;
package/lib/user.d.ts CHANGED
@@ -101,10 +101,10 @@ export declare class User {
101
101
  getPerpBuyingPower(marketIndex: number, collateralBuffer?: any): BN;
102
102
  getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount(marketIndex: number, freeCollateral: BN, baseAssetAmount: BN): BN;
103
103
  /**
104
- * calculates Free Collateral = Total collateral - initial margin requirement
104
+ * calculates Free Collateral = Total collateral - margin requirement
105
105
  * @returns : Precision QUOTE_PRECISION
106
106
  */
107
- getFreeCollateral(): BN;
107
+ getFreeCollateral(marginCategory?: MarginCategory): BN;
108
108
  /**
109
109
  * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
110
110
  */
package/lib/user.js CHANGED
@@ -145,6 +145,7 @@ class User {
145
145
  lpShares: numericConstants_1.ZERO,
146
146
  lastBaseAssetAmountPerLp: numericConstants_1.ZERO,
147
147
  lastQuoteAssetAmountPerLp: numericConstants_1.ZERO,
148
+ perLpBase: 0,
148
149
  };
149
150
  }
150
151
  getClonedPosition(position) {
@@ -258,17 +259,54 @@ class User {
258
259
  }
259
260
  const position = this.getClonedPosition(originalPosition);
260
261
  const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
262
+ if (market.amm.perLpBase != position.perLpBase) {
263
+ // perLpBase = 1 => per 10 LP shares, perLpBase = -1 => per 0.1 LP shares
264
+ const expoDiff = market.amm.perLpBase - position.perLpBase;
265
+ const marketPerLpRebaseScalar = new _1.BN(10 ** Math.abs(expoDiff));
266
+ if (expoDiff > 0) {
267
+ position.lastBaseAssetAmountPerLp =
268
+ position.lastBaseAssetAmountPerLp.mul(marketPerLpRebaseScalar);
269
+ position.lastQuoteAssetAmountPerLp =
270
+ position.lastQuoteAssetAmountPerLp.mul(marketPerLpRebaseScalar);
271
+ }
272
+ else {
273
+ position.lastBaseAssetAmountPerLp =
274
+ position.lastBaseAssetAmountPerLp.div(marketPerLpRebaseScalar);
275
+ position.lastQuoteAssetAmountPerLp =
276
+ position.lastQuoteAssetAmountPerLp.div(marketPerLpRebaseScalar);
277
+ }
278
+ position.perLpBase = position.perLpBase + expoDiff;
279
+ }
261
280
  const nShares = position.lpShares;
262
281
  // incorp unsettled funding on pre settled position
263
282
  const quoteFundingPnl = (0, _1.calculatePositionFundingPNL)(market, position);
283
+ let baseUnit = numericConstants_1.AMM_RESERVE_PRECISION;
284
+ if (market.amm.perLpBase == position.perLpBase) {
285
+ if (position.perLpBase >= 0 &&
286
+ position.perLpBase <= numericConstants_1.AMM_RESERVE_PRECISION_EXP.toNumber()) {
287
+ const marketPerLpRebase = new _1.BN(10 ** market.amm.perLpBase);
288
+ baseUnit = baseUnit.mul(marketPerLpRebase);
289
+ }
290
+ else if (position.perLpBase < 0 &&
291
+ position.perLpBase >= -numericConstants_1.AMM_RESERVE_PRECISION_EXP.toNumber()) {
292
+ const marketPerLpRebase = new _1.BN(10 ** Math.abs(market.amm.perLpBase));
293
+ baseUnit = baseUnit.div(marketPerLpRebase);
294
+ }
295
+ else {
296
+ throw 'cannot calc';
297
+ }
298
+ }
299
+ else {
300
+ throw 'market.amm.perLpBase != position.perLpBase';
301
+ }
264
302
  const deltaBaa = market.amm.baseAssetAmountPerLp
265
303
  .sub(position.lastBaseAssetAmountPerLp)
266
304
  .mul(nShares)
267
- .div(numericConstants_1.AMM_RESERVE_PRECISION);
305
+ .div(baseUnit);
268
306
  const deltaQaa = market.amm.quoteAssetAmountPerLp
269
307
  .sub(position.lastQuoteAssetAmountPerLp)
270
308
  .mul(nShares)
271
- .div(numericConstants_1.AMM_RESERVE_PRECISION);
309
+ .div(baseUnit);
272
310
  function sign(v) {
273
311
  return v.isNeg() ? new _1.BN(-1) : new _1.BN(1);
274
312
  }
@@ -376,13 +414,15 @@ class User {
376
414
  return freeCollateral.mul(numericConstants_1.MARGIN_PRECISION).div(new _1.BN(marginRatio));
377
415
  }
378
416
  /**
379
- * calculates Free Collateral = Total collateral - initial margin requirement
417
+ * calculates Free Collateral = Total collateral - margin requirement
380
418
  * @returns : Precision QUOTE_PRECISION
381
419
  */
382
- getFreeCollateral() {
383
- const totalCollateral = this.getTotalCollateral('Initial', true);
384
- const initialMarginRequirement = this.getInitialMarginRequirement();
385
- const freeCollateral = totalCollateral.sub(initialMarginRequirement);
420
+ getFreeCollateral(marginCategory = 'Initial') {
421
+ const totalCollateral = this.getTotalCollateral(marginCategory, true);
422
+ const marginRequirement = marginCategory === 'Initial'
423
+ ? this.getInitialMarginRequirement()
424
+ : this.getMaintenanceMarginRequirement();
425
+ const freeCollateral = totalCollateral.sub(marginRequirement);
386
426
  return freeCollateral.gte(numericConstants_1.ZERO) ? freeCollateral : numericConstants_1.ZERO;
387
427
  }
388
428
  /**
@@ -648,15 +688,8 @@ class User {
648
688
  health = 0;
649
689
  }
650
690
  else {
651
- const healthP1 = Math.max(0, (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
652
- 100) + 1;
653
- health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
654
- if (health > 1) {
655
- health = Math.round(health);
656
- }
657
- else {
658
- health = Math.round(health * 100) / 100;
659
- }
691
+ health = Math.round(Math.min(100, Math.max(0, (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
692
+ 100)));
660
693
  }
661
694
  return health;
662
695
  }
@@ -721,12 +754,7 @@ class User {
721
754
  */
722
755
  getPerpMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
723
756
  const perpPosition = this.getPerpPosition(marketIndex);
724
- if (!perpPosition) {
725
- return numericConstants_1.ZERO;
726
- }
727
- else {
728
- return this.calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict);
729
- }
757
+ return this.calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict);
730
758
  }
731
759
  /**
732
760
  * calculates sum of position value across all positions in margin system
@@ -1779,7 +1807,7 @@ class User {
1779
1807
  healthComponents.perpPnl.push({
1780
1808
  marketIndex: perpMarket.marketIndex,
1781
1809
  size: positionUnrealizedPnl,
1782
- value: wegithedPnlValue,
1810
+ value: pnlValue,
1783
1811
  weight: pnlWeight,
1784
1812
  weightedValue: wegithedPnlValue,
1785
1813
  });
@@ -1820,7 +1848,7 @@ class User {
1820
1848
  healthComponents.deposits.push({
1821
1849
  marketIndex: spotMarketAccount.marketIndex,
1822
1850
  size: worstCaseTokenAmount,
1823
- value: weightedValue,
1851
+ value: baseAssetValue,
1824
1852
  weight: weight,
1825
1853
  weightedValue: weightedValue,
1826
1854
  });
@@ -1854,7 +1882,7 @@ class User {
1854
1882
  healthComponents.deposits.push({
1855
1883
  marketIndex: spotMarketAccount.marketIndex,
1856
1884
  size: netQuoteValue,
1857
- value: weightedValue,
1885
+ value: baseAssetValue,
1858
1886
  weight: weight,
1859
1887
  weightedValue: weightedValue,
1860
1888
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.37.1-beta.8",
3
+ "version": "2.38.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -229,7 +229,17 @@ export class BulkAccountLoader {
229
229
  slot: number
230
230
  ): void {
231
231
  for (const [_, callback] of accountToLoad.callbacks) {
232
- callback(buffer, slot);
232
+ try {
233
+ callback(buffer, slot);
234
+ } catch (e) {
235
+ console.log('Bulk account load: error in account callback');
236
+ console.log('accounto to load', accountToLoad.publicKey.toString());
237
+ console.log('buffer', buffer.toString('base64'));
238
+ for (const callback of accountToLoad.callbacks.values()) {
239
+ console.log('account to load cb', callback);
240
+ }
241
+ throw e;
242
+ }
233
243
  }
234
244
  }
235
245
 
@@ -691,6 +691,24 @@ export class AdminClient extends DriftClient {
691
691
  });
692
692
  }
693
693
 
694
+ public async updatePerpMarketPerLpBase(
695
+ perpMarketIndex: number,
696
+ perLpBase: number
697
+ ): Promise<TransactionSignature> {
698
+ const perpMarketPublicKey = await getPerpMarketPublicKey(
699
+ this.program.programId,
700
+ perpMarketIndex
701
+ );
702
+
703
+ return await this.program.rpc.updatePerpMarketPerLpBase(perLpBase, {
704
+ accounts: {
705
+ admin: this.wallet.publicKey,
706
+ state: await this.getStatePublicKey(),
707
+ perpMarket: perpMarketPublicKey,
708
+ },
709
+ });
710
+ }
711
+
694
712
  public async updatePerpMarketMaxSpread(
695
713
  perpMarketIndex: number,
696
714
  maxSpread: number
@@ -63,7 +63,8 @@ export class EventSubscriber {
63
63
  this.connection,
64
64
  this.address,
65
65
  options.commitment,
66
- this.options.logProviderConfig.frequency
66
+ this.options.logProviderConfig.frequency,
67
+ this.options.logProviderConfig.batchSize
67
68
  );
68
69
  }
69
70
  }
@@ -36,7 +36,8 @@ export async function fetchLogs(
36
36
  finality: Finality,
37
37
  beforeTx?: TransactionSignature,
38
38
  untilTx?: TransactionSignature,
39
- limit?: number
39
+ limit?: number,
40
+ batchSize = 25
40
41
  ): Promise<FetchLogsResponse> {
41
42
  const signatures = await connection.getSignaturesForAddress(
42
43
  address,
@@ -60,7 +61,7 @@ export async function fetchLogs(
60
61
  return undefined;
61
62
  }
62
63
 
63
- const chunkedSignatures = chunk(filteredSignatures, 100);
64
+ const chunkedSignatures = chunk(filteredSignatures, batchSize);
64
65
 
65
66
  const transactionLogs = (
66
67
  await Promise.all(
@@ -19,7 +19,8 @@ export class PollingLogProvider implements LogProvider {
19
19
  private connection: Connection,
20
20
  private address: PublicKey,
21
21
  commitment: Commitment,
22
- private frequency = 15 * 1000
22
+ private frequency = 15 * 1000,
23
+ private batchSize?: number
23
24
  ) {
24
25
  this.finality = commitment === 'finalized' ? 'finalized' : 'confirmed';
25
26
  }
@@ -46,7 +47,8 @@ export class PollingLogProvider implements LogProvider {
46
47
  undefined,
47
48
  this.mostRecentSeenTx,
48
49
  // If skipping history, only fetch one log back, not the maximum amount available
49
- skipHistory && this.firstFetch ? 1 : undefined
50
+ skipHistory && this.firstFetch ? 1 : undefined,
51
+ this.batchSize
50
52
  );
51
53
 
52
54
  if (response === undefined) {
@@ -137,6 +137,7 @@ export type WebSocketLogProviderConfig = {
137
137
  export type PollingLogProviderConfig = {
138
138
  type: 'polling';
139
139
  frequency: number;
140
+ batchSize?: number;
140
141
  };
141
142
 
142
143
  export type LogProviderConfig =
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.37.0",
2
+ "version": "2.38.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -3882,6 +3882,32 @@
3882
3882
  }
3883
3883
  ]
3884
3884
  },
3885
+ {
3886
+ "name": "updatePerpMarketPerLpBase",
3887
+ "accounts": [
3888
+ {
3889
+ "name": "admin",
3890
+ "isMut": false,
3891
+ "isSigner": true
3892
+ },
3893
+ {
3894
+ "name": "state",
3895
+ "isMut": false,
3896
+ "isSigner": false
3897
+ },
3898
+ {
3899
+ "name": "perpMarket",
3900
+ "isMut": true,
3901
+ "isSigner": false
3902
+ }
3903
+ ],
3904
+ "args": [
3905
+ {
3906
+ "name": "perLpBase",
3907
+ "type": "i8"
3908
+ }
3909
+ ]
3910
+ },
3885
3911
  {
3886
3912
  "name": "updateLpCooldownTime",
3887
3913
  "accounts": [
@@ -6978,9 +7004,20 @@
6978
7004
  ],
6979
7005
  "type": "i32"
6980
7006
  },
7007
+ {
7008
+ "name": "perLpBase",
7009
+ "docs": [
7010
+ "expo for unit of per_lp, base 10 (if per_lp_base=X, then per_lp unit is 10^X)"
7011
+ ],
7012
+ "type": "i8"
7013
+ },
6981
7014
  {
6982
7015
  "name": "padding1",
6983
- "type": "u32"
7016
+ "type": "u8"
7017
+ },
7018
+ {
7019
+ "name": "padding2",
7020
+ "type": "u16"
6984
7021
  },
6985
7022
  {
6986
7023
  "name": "totalFeeEarnedPerLp",
@@ -7447,13 +7484,8 @@
7447
7484
  "type": "u8"
7448
7485
  },
7449
7486
  {
7450
- "name": "padding",
7451
- "type": {
7452
- "array": [
7453
- "u8",
7454
- 1
7455
- ]
7456
- }
7487
+ "name": "perLpBase",
7488
+ "type": "i8"
7457
7489
  }
7458
7490
  ]
7459
7491
  }
package/src/types.ts CHANGED
@@ -767,6 +767,8 @@ export type AMM = {
767
767
  bidQuoteAssetReserve: BN;
768
768
  askBaseAssetReserve: BN;
769
769
  askQuoteAssetReserve: BN;
770
+
771
+ perLpBase: number; // i8
770
772
  };
771
773
 
772
774
  // # User Account Types
@@ -785,6 +787,7 @@ export type PerpPosition = {
785
787
  remainderBaseAssetAmount: number;
786
788
  lastBaseAssetAmountPerLp: BN;
787
789
  lastQuoteAssetAmountPerLp: BN;
790
+ perLpBase: number;
788
791
  };
789
792
 
790
793
  export type UserStatsAccount = {
package/src/user.ts CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  BASE_PRECISION,
32
32
  ONE,
33
33
  TWO,
34
+ AMM_RESERVE_PRECISION_EXP,
34
35
  } from './constants/numericConstants';
35
36
  import {
36
37
  UserAccountSubscriber,
@@ -264,6 +265,7 @@ export class User {
264
265
  lpShares: ZERO,
265
266
  lastBaseAssetAmountPerLp: ZERO,
266
267
  lastQuoteAssetAmountPerLp: ZERO,
268
+ perLpBase: 0,
267
269
  };
268
270
  }
269
271
 
@@ -427,21 +429,62 @@ export class User {
427
429
  }
428
430
 
429
431
  const position = this.getClonedPosition(originalPosition);
430
-
431
432
  const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
433
+
434
+ if (market.amm.perLpBase != position.perLpBase) {
435
+ // perLpBase = 1 => per 10 LP shares, perLpBase = -1 => per 0.1 LP shares
436
+ const expoDiff = market.amm.perLpBase - position.perLpBase;
437
+ const marketPerLpRebaseScalar = new BN(10 ** Math.abs(expoDiff));
438
+
439
+ if (expoDiff > 0) {
440
+ position.lastBaseAssetAmountPerLp =
441
+ position.lastBaseAssetAmountPerLp.mul(marketPerLpRebaseScalar);
442
+ position.lastQuoteAssetAmountPerLp =
443
+ position.lastQuoteAssetAmountPerLp.mul(marketPerLpRebaseScalar);
444
+ } else {
445
+ position.lastBaseAssetAmountPerLp =
446
+ position.lastBaseAssetAmountPerLp.div(marketPerLpRebaseScalar);
447
+ position.lastQuoteAssetAmountPerLp =
448
+ position.lastQuoteAssetAmountPerLp.div(marketPerLpRebaseScalar);
449
+ }
450
+
451
+ position.perLpBase = position.perLpBase + expoDiff;
452
+ }
453
+
432
454
  const nShares = position.lpShares;
433
455
 
434
456
  // incorp unsettled funding on pre settled position
435
457
  const quoteFundingPnl = calculatePositionFundingPNL(market, position);
436
458
 
459
+ let baseUnit = AMM_RESERVE_PRECISION;
460
+ if (market.amm.perLpBase == position.perLpBase) {
461
+ if (
462
+ position.perLpBase >= 0 &&
463
+ position.perLpBase <= AMM_RESERVE_PRECISION_EXP.toNumber()
464
+ ) {
465
+ const marketPerLpRebase = new BN(10 ** market.amm.perLpBase);
466
+ baseUnit = baseUnit.mul(marketPerLpRebase);
467
+ } else if (
468
+ position.perLpBase < 0 &&
469
+ position.perLpBase >= -AMM_RESERVE_PRECISION_EXP.toNumber()
470
+ ) {
471
+ const marketPerLpRebase = new BN(10 ** Math.abs(market.amm.perLpBase));
472
+ baseUnit = baseUnit.div(marketPerLpRebase);
473
+ } else {
474
+ throw 'cannot calc';
475
+ }
476
+ } else {
477
+ throw 'market.amm.perLpBase != position.perLpBase';
478
+ }
479
+
437
480
  const deltaBaa = market.amm.baseAssetAmountPerLp
438
481
  .sub(position.lastBaseAssetAmountPerLp)
439
482
  .mul(nShares)
440
- .div(AMM_RESERVE_PRECISION);
483
+ .div(baseUnit);
441
484
  const deltaQaa = market.amm.quoteAssetAmountPerLp
442
485
  .sub(position.lastQuoteAssetAmountPerLp)
443
486
  .mul(nShares)
444
- .div(AMM_RESERVE_PRECISION);
487
+ .div(baseUnit);
445
488
 
446
489
  function sign(v: BN) {
447
490
  return v.isNeg() ? new BN(-1) : new BN(1);
@@ -593,13 +636,16 @@ export class User {
593
636
  }
594
637
 
595
638
  /**
596
- * calculates Free Collateral = Total collateral - initial margin requirement
639
+ * calculates Free Collateral = Total collateral - margin requirement
597
640
  * @returns : Precision QUOTE_PRECISION
598
641
  */
599
- public getFreeCollateral(): BN {
600
- const totalCollateral = this.getTotalCollateral('Initial', true);
601
- const initialMarginRequirement = this.getInitialMarginRequirement();
602
- const freeCollateral = totalCollateral.sub(initialMarginRequirement);
642
+ public getFreeCollateral(marginCategory: MarginCategory = 'Initial'): BN {
643
+ const totalCollateral = this.getTotalCollateral(marginCategory, true);
644
+ const marginRequirement =
645
+ marginCategory === 'Initial'
646
+ ? this.getInitialMarginRequirement()
647
+ : this.getMaintenanceMarginRequirement();
648
+ const freeCollateral = totalCollateral.sub(marginRequirement);
603
649
  return freeCollateral.gte(ZERO) ? freeCollateral : ZERO;
604
650
  }
605
651
 
@@ -1179,19 +1225,16 @@ export class User {
1179
1225
  } else if (totalCollateral.lte(ZERO)) {
1180
1226
  health = 0;
1181
1227
  } else {
1182
- const healthP1 =
1183
- Math.max(
1184
- 0,
1185
- (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
1186
- 100
1187
- ) + 1;
1188
-
1189
- health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
1190
- if (health > 1) {
1191
- health = Math.round(health);
1192
- } else {
1193
- health = Math.round(health * 100) / 100;
1194
- }
1228
+ health = Math.round(
1229
+ Math.min(
1230
+ 100,
1231
+ Math.max(
1232
+ 0,
1233
+ (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
1234
+ 100
1235
+ )
1236
+ )
1237
+ );
1195
1238
  }
1196
1239
 
1197
1240
  return health;
@@ -1316,17 +1359,13 @@ export class User {
1316
1359
  strict = false
1317
1360
  ): BN {
1318
1361
  const perpPosition = this.getPerpPosition(marketIndex);
1319
- if (!perpPosition) {
1320
- return ZERO;
1321
- } else {
1322
- return this.calculateWeightedPerpPositionValue(
1323
- perpPosition,
1324
- marginCategory,
1325
- liquidationBuffer,
1326
- includeOpenOrders,
1327
- strict
1328
- );
1329
- }
1362
+ return this.calculateWeightedPerpPositionValue(
1363
+ perpPosition,
1364
+ marginCategory,
1365
+ liquidationBuffer,
1366
+ includeOpenOrders,
1367
+ strict
1368
+ );
1330
1369
  }
1331
1370
 
1332
1371
  /**
@@ -3228,7 +3267,7 @@ export class User {
3228
3267
  healthComponents.perpPnl.push({
3229
3268
  marketIndex: perpMarket.marketIndex,
3230
3269
  size: positionUnrealizedPnl,
3231
- value: wegithedPnlValue,
3270
+ value: pnlValue,
3232
3271
  weight: pnlWeight,
3233
3272
  weightedValue: wegithedPnlValue,
3234
3273
  });
@@ -3304,7 +3343,7 @@ export class User {
3304
3343
  healthComponents.deposits.push({
3305
3344
  marketIndex: spotMarketAccount.marketIndex,
3306
3345
  size: worstCaseTokenAmount,
3307
- value: weightedValue,
3346
+ value: baseAssetValue,
3308
3347
  weight: weight,
3309
3348
  weightedValue: weightedValue,
3310
3349
  });
@@ -3355,7 +3394,7 @@ export class User {
3355
3394
  healthComponents.deposits.push({
3356
3395
  marketIndex: spotMarketAccount.marketIndex,
3357
3396
  size: netQuoteValue,
3358
- value: weightedValue,
3397
+ value: baseAssetValue,
3359
3398
  weight: weight,
3360
3399
  weightedValue: weightedValue,
3361
3400
  });
@@ -39,9 +39,11 @@ export const mockPerpPosition: PerpPosition = {
39
39
  remainderBaseAssetAmount: 0,
40
40
  lastBaseAssetAmountPerLp: new BN(0),
41
41
  lastQuoteAssetAmountPerLp: new BN(0),
42
+ perLpBase: 0,
42
43
  };
43
44
 
44
45
  export const mockAMM: AMM = {
46
+ perLpBase: 0,
45
47
  /* these values create a bid/ask price of 12 */
46
48
  baseAssetReserve: new BN(1).mul(BASE_PRECISION),
47
49
  quoteAssetReserve: new BN(12)