@drift-labs/sdk 2.52.0-beta.0 → 2.52.0-beta.10

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 (100) hide show
  1. package/VERSION +1 -1
  2. package/examples/phoenix.ts +11 -4
  3. package/lib/accounts/basicUserAccountSubscriber.d.ts +4 -0
  4. package/lib/accounts/basicUserAccountSubscriber.js +4 -0
  5. package/lib/accounts/bulkAccountLoader.js +7 -1
  6. package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
  7. package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
  8. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
  9. package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
  10. package/lib/accounts/webSocketAccountSubscriber.js +7 -4
  11. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
  12. package/lib/accounts/webSocketProgramAccountSubscriber.js +7 -4
  13. package/lib/adminClient.d.ts +1 -0
  14. package/lib/adminClient.js +9 -0
  15. package/lib/constants/numericConstants.d.ts +3 -0
  16. package/lib/constants/numericConstants.js +4 -1
  17. package/lib/dlob/orderBookLevels.d.ts +22 -0
  18. package/lib/dlob/orderBookLevels.js +115 -1
  19. package/lib/driftClient.d.ts +3 -0
  20. package/lib/driftClient.js +31 -5
  21. package/lib/events/webSocketLogProvider.js +3 -3
  22. package/lib/factory/bigNum.d.ts +1 -1
  23. package/lib/factory/bigNum.js +5 -2
  24. package/lib/idl/drift.json +52 -1
  25. package/lib/index.d.ts +2 -1
  26. package/lib/index.js +2 -1
  27. package/lib/math/amm.d.ts +5 -1
  28. package/lib/math/amm.js +62 -13
  29. package/lib/math/state.js +3 -0
  30. package/lib/orderSubscriber/OrderSubscriber.js +1 -0
  31. package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -1
  32. package/lib/orderSubscriber/WebsocketSubscription.js +25 -1
  33. package/lib/orderSubscriber/types.d.ts +1 -0
  34. package/lib/phoenix/phoenixSubscriber.js +10 -6
  35. package/lib/priorityFee/averageOverSlotsStrategy.d.ts +12 -0
  36. package/lib/priorityFee/averageOverSlotsStrategy.js +28 -0
  37. package/lib/priorityFee/averageStrategy.d.ts +7 -0
  38. package/lib/priorityFee/averageStrategy.js +11 -0
  39. package/lib/priorityFee/ewmaStrategy.d.ts +13 -0
  40. package/lib/priorityFee/ewmaStrategy.js +33 -0
  41. package/lib/priorityFee/index.d.ts +7 -0
  42. package/lib/priorityFee/index.js +23 -0
  43. package/lib/priorityFee/maxOverSlotsStrategy.d.ts +12 -0
  44. package/lib/priorityFee/maxOverSlotsStrategy.js +29 -0
  45. package/lib/priorityFee/maxStrategy.d.ts +7 -0
  46. package/lib/priorityFee/maxStrategy.js +9 -0
  47. package/lib/priorityFee/priorityFeeSubscriber.d.ts +15 -4
  48. package/lib/priorityFee/priorityFeeSubscriber.js +38 -14
  49. package/lib/priorityFee/types.d.ts +6 -0
  50. package/lib/priorityFee/types.js +2 -0
  51. package/lib/slot/SlotSubscriber.d.ts +11 -3
  52. package/lib/slot/SlotSubscriber.js +40 -4
  53. package/lib/types.d.ts +4 -1
  54. package/lib/types.js +1 -0
  55. package/lib/user.d.ts +1 -1
  56. package/lib/user.js +15 -8
  57. package/lib/userMap/userMap.d.ts +3 -0
  58. package/lib/userMap/userMap.js +10 -1
  59. package/lib/userStats.d.ts +1 -0
  60. package/lib/userStats.js +3 -0
  61. package/package.json +1 -1
  62. package/src/accounts/basicUserAccountSubscriber.ts +4 -0
  63. package/src/accounts/bulkAccountLoader.ts +10 -3
  64. package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
  65. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
  66. package/src/accounts/webSocketAccountSubscriber.ts +7 -4
  67. package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
  68. package/src/adminClient.ts +13 -0
  69. package/src/constants/numericConstants.ts +4 -0
  70. package/src/dlob/orderBookLevels.ts +136 -0
  71. package/src/driftClient.ts +50 -6
  72. package/src/events/webSocketLogProvider.ts +3 -3
  73. package/src/factory/bigNum.ts +11 -2
  74. package/src/idl/drift.json +52 -1
  75. package/src/index.ts +2 -1
  76. package/src/math/amm.ts +159 -25
  77. package/src/math/state.ts +3 -0
  78. package/src/orderSubscriber/OrderSubscriber.ts +1 -0
  79. package/src/orderSubscriber/WebsocketSubscription.ts +28 -0
  80. package/src/orderSubscriber/types.ts +1 -0
  81. package/src/phoenix/phoenixSubscriber.ts +14 -8
  82. package/src/priorityFee/averageOverSlotsStrategy.ts +30 -0
  83. package/src/priorityFee/averageStrategy.ts +11 -0
  84. package/src/priorityFee/ewmaStrategy.ts +40 -0
  85. package/src/priorityFee/index.ts +7 -0
  86. package/src/priorityFee/maxOverSlotsStrategy.ts +31 -0
  87. package/src/priorityFee/maxStrategy.ts +7 -0
  88. package/src/priorityFee/priorityFeeSubscriber.ts +46 -19
  89. package/src/priorityFee/types.ts +5 -0
  90. package/src/slot/SlotSubscriber.ts +52 -5
  91. package/src/types.ts +2 -1
  92. package/src/user.ts +25 -8
  93. package/src/userMap/userMap.ts +17 -3
  94. package/src/userStats.ts +8 -0
  95. package/tests/amm/test.ts +219 -11
  96. package/tests/bn/test.ts +27 -0
  97. package/tests/dlob/helpers.ts +1 -1
  98. package/tests/dlob/test.ts +372 -2
  99. package/tests/tx/priorityFeeStrategy.ts +97 -0
  100. package/tests/user/helpers.ts +1 -0
@@ -407,3 +407,139 @@ function groupL2Levels(
407
407
  }
408
408
  return groupedLevels;
409
409
  }
410
+
411
+ /**
412
+ * The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
413
+ * This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
414
+ * crossing book)
415
+ *
416
+ * Things to note about how it works:
417
+ * - it will not uncross the user's liquidity
418
+ * - it does the uncrossing by "shifting" the crossing liquidity to the nearest uncrossed levels. Thus the output liquidity maintains the same total size.
419
+ *
420
+ * @param bids
421
+ * @param asks
422
+ * @param oraclePrice
423
+ * @param oracleTwap5Min
424
+ * @param markTwap5Min
425
+ * @param grouping
426
+ * @param userBids
427
+ * @param userAsks
428
+ */
429
+ export function uncrossL2(
430
+ bids: L2Level[],
431
+ asks: L2Level[],
432
+ oraclePrice: BN,
433
+ oracleTwap5Min: BN,
434
+ markTwap5Min: BN,
435
+ grouping: BN,
436
+ userBids: Set<string>,
437
+ userAsks: Set<string>
438
+ ): { bids: L2Level[]; asks: L2Level[] } {
439
+ // If there are no bids or asks, there is nothing to center
440
+ if (bids.length === 0 || asks.length === 0) {
441
+ return { bids, asks };
442
+ }
443
+
444
+ // If the top of the book is already centered, there is nothing to do
445
+ if (bids[0].price.lt(asks[0].price)) {
446
+ return { bids, asks };
447
+ }
448
+
449
+ const newBids = [];
450
+ const newAsks = [];
451
+
452
+ const updateLevels = (newPrice: BN, oldLevel: L2Level, levels: L2Level[]) => {
453
+ if (levels.length > 0 && levels[levels.length - 1].price.eq(newPrice)) {
454
+ levels[levels.length - 1].size = levels[levels.length - 1].size.add(
455
+ oldLevel.size
456
+ );
457
+ for (const [source, size] of Object.entries(oldLevel.sources)) {
458
+ if (levels[levels.length - 1].sources[source]) {
459
+ levels[levels.length - 1].sources = {
460
+ ...levels[levels.length - 1].sources,
461
+ [source]: levels[levels.length - 1].sources[source].add(size),
462
+ };
463
+ } else {
464
+ levels[levels.length - 1].sources[source] = size;
465
+ }
466
+ }
467
+ } else {
468
+ levels.push({
469
+ price: newPrice,
470
+ size: oldLevel.size,
471
+ sources: oldLevel.sources,
472
+ });
473
+ }
474
+ };
475
+
476
+ // This is the best estimate of the premium in the market vs oracle to filter crossing around
477
+ const referencePrice = oraclePrice.add(markTwap5Min.sub(oracleTwap5Min));
478
+
479
+ let bidIndex = 0;
480
+ let askIndex = 0;
481
+ while (bidIndex < bids.length || askIndex < asks.length) {
482
+ const nextBid = bids[bidIndex];
483
+ const nextAsk = asks[askIndex];
484
+
485
+ if (!nextBid) {
486
+ newAsks.push(nextAsk);
487
+ askIndex++;
488
+ continue;
489
+ }
490
+
491
+ if (!nextAsk) {
492
+ newBids.push(nextBid);
493
+ bidIndex++;
494
+ continue;
495
+ }
496
+
497
+ if (nextBid.price.gt(nextAsk.price)) {
498
+ if (userBids.has(nextBid.price.toString())) {
499
+ newBids.push(nextBid);
500
+ bidIndex++;
501
+ continue;
502
+ }
503
+
504
+ if (userAsks.has(nextAsk.price.toString())) {
505
+ newAsks.push(nextAsk);
506
+ askIndex++;
507
+ continue;
508
+ }
509
+
510
+ if (
511
+ nextBid.price.gt(referencePrice) &&
512
+ nextAsk.price.gt(referencePrice)
513
+ ) {
514
+ const newBidPrice = nextAsk.price.sub(grouping);
515
+ updateLevels(newBidPrice, nextBid, newBids);
516
+ bidIndex++;
517
+ } else if (
518
+ nextAsk.price.lt(referencePrice) &&
519
+ nextBid.price.lt(referencePrice)
520
+ ) {
521
+ const newAskPrice = nextBid.price.add(grouping);
522
+ updateLevels(newAskPrice, nextAsk, newAsks);
523
+ askIndex++;
524
+ } else {
525
+ const newBidPrice = referencePrice.sub(grouping);
526
+ const newAskPrice = referencePrice.add(grouping);
527
+ updateLevels(newBidPrice, nextBid, newBids);
528
+ updateLevels(newAskPrice, nextAsk, newAsks);
529
+ bidIndex++;
530
+ askIndex++;
531
+ }
532
+ } else {
533
+ newAsks.push(nextAsk);
534
+ askIndex++;
535
+
536
+ newBids.push(nextBid);
537
+ bidIndex++;
538
+ }
539
+ }
540
+
541
+ return {
542
+ bids: newBids,
543
+ asks: newAsks,
544
+ };
545
+ }
@@ -1122,6 +1122,22 @@ export class DriftClient {
1122
1122
  subAccountId
1123
1123
  );
1124
1124
 
1125
+ const ix = await this.getUserDeletionIx(userAccountPublicKey);
1126
+
1127
+ const { txSig } = await this.sendTransaction(
1128
+ await this.buildTransaction(ix, txParams),
1129
+ [],
1130
+ this.opts
1131
+ );
1132
+
1133
+ const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
1134
+ await this.users.get(userMapKey)?.unsubscribe();
1135
+ this.users.delete(userMapKey);
1136
+
1137
+ return txSig;
1138
+ }
1139
+
1140
+ public async getUserDeletionIx(userAccountPublicKey: PublicKey) {
1125
1141
  const ix = await this.program.instruction.deleteUser({
1126
1142
  accounts: {
1127
1143
  user: userAccountPublicKey,
@@ -1131,19 +1147,42 @@ export class DriftClient {
1131
1147
  },
1132
1148
  });
1133
1149
 
1150
+ return ix;
1151
+ }
1152
+
1153
+ public async reclaimRent(
1154
+ subAccountId = 0,
1155
+ txParams?: TxParams
1156
+ ): Promise<TransactionSignature> {
1157
+ const userAccountPublicKey = getUserAccountPublicKeySync(
1158
+ this.program.programId,
1159
+ this.wallet.publicKey,
1160
+ subAccountId
1161
+ );
1162
+
1163
+ const ix = await this.getReclaimRentIx(userAccountPublicKey);
1164
+
1134
1165
  const { txSig } = await this.sendTransaction(
1135
1166
  await this.buildTransaction(ix, txParams),
1136
1167
  [],
1137
1168
  this.opts
1138
1169
  );
1139
1170
 
1140
- const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
1141
- await this.users.get(userMapKey)?.unsubscribe();
1142
- this.users.delete(userMapKey);
1143
-
1144
1171
  return txSig;
1145
1172
  }
1146
1173
 
1174
+ public async getReclaimRentIx(userAccountPublicKey: PublicKey) {
1175
+ return await this.program.instruction.reclaimRent({
1176
+ accounts: {
1177
+ user: userAccountPublicKey,
1178
+ userStats: this.getUserStatsAccountPublicKey(),
1179
+ authority: this.wallet.publicKey,
1180
+ state: await this.getStatePublicKey(),
1181
+ rent: anchor.web3.SYSVAR_RENT_PUBKEY,
1182
+ },
1183
+ });
1184
+ }
1185
+
1147
1186
  public getUser(subAccountId?: number, authority?: PublicKey): User {
1148
1187
  subAccountId = subAccountId ?? this.activeSubAccountId;
1149
1188
  authority = authority ?? this.authority;
@@ -3953,9 +3992,14 @@ export class DriftClient {
3953
3992
  userAccountPublicKey || (await this.getUserAccountPublicKey());
3954
3993
 
3955
3994
  const userAccounts = [];
3956
- if (this.hasUser() && this.getUser().getUserAccountAndSlot()) {
3957
- userAccounts.push(this.getUser().getUserAccountAndSlot()!.data);
3995
+ try {
3996
+ if (this.hasUser() && this.getUser().getUserAccountAndSlot()) {
3997
+ userAccounts.push(this.getUser().getUserAccountAndSlot()!.data);
3998
+ }
3999
+ } catch (err) {
4000
+ // ignore
3958
4001
  }
4002
+
3959
4003
  const remainingAccounts = this.getRemainingAccounts({
3960
4004
  userAccounts,
3961
4005
  writableSpotMarketIndexes: [outMarketIndex, inMarketIndex],
@@ -23,7 +23,7 @@ export class WebSocketLogProvider implements LogProvider {
23
23
  }
24
24
 
25
25
  public async subscribe(callback: logProviderCallback): Promise<boolean> {
26
- if (this.subscriptionId) {
26
+ if (this.subscriptionId != null) {
27
27
  return true;
28
28
  }
29
29
 
@@ -62,7 +62,7 @@ export class WebSocketLogProvider implements LogProvider {
62
62
  }
63
63
 
64
64
  public isSubscribed(): boolean {
65
- return this.subscriptionId !== undefined;
65
+ return this.subscriptionId != null;
66
66
  }
67
67
 
68
68
  public async unsubscribe(external = false): Promise<boolean> {
@@ -71,7 +71,7 @@ export class WebSocketLogProvider implements LogProvider {
71
71
  clearTimeout(this.timeoutId);
72
72
  this.timeoutId = undefined;
73
73
 
74
- if (this.subscriptionId !== undefined) {
74
+ if (this.subscriptionId != null) {
75
75
  try {
76
76
  await this.connection.removeOnLogsListener(this.subscriptionId);
77
77
  this.subscriptionId = undefined;
@@ -497,7 +497,11 @@ export class BigNum {
497
497
  return `${prefix}${val.replace('-', '')}`;
498
498
  }
499
499
 
500
- public toMillified(precision = 3, rounded = false): string {
500
+ public toMillified(
501
+ precision = 3,
502
+ rounded = false,
503
+ type: 'financial' | 'scientific' = 'financial'
504
+ ): string {
501
505
  if (rounded) {
502
506
  return this.toRounded(precision).toMillified(precision);
503
507
  }
@@ -520,7 +524,12 @@ export class BigNum {
520
524
  return this.shift(new BN(precision)).toPrecision(precision, true);
521
525
  }
522
526
 
523
- const unitTicks = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
527
+ const unitTicks =
528
+ type === 'financial'
529
+ ? ['', 'K', 'M', 'B', 'T', 'Q']
530
+ : ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
531
+ // TODO -- handle nubers which are larger than the max unit tick
532
+
524
533
  const unitNumber = Math.floor((leftSide.length - 1) / 3);
525
534
  const unit = unitTicks[unitNumber];
526
535
 
@@ -1173,6 +1173,37 @@
1173
1173
  ],
1174
1174
  "args": []
1175
1175
  },
1176
+ {
1177
+ "name": "reclaimRent",
1178
+ "accounts": [
1179
+ {
1180
+ "name": "user",
1181
+ "isMut": true,
1182
+ "isSigner": false
1183
+ },
1184
+ {
1185
+ "name": "userStats",
1186
+ "isMut": true,
1187
+ "isSigner": false
1188
+ },
1189
+ {
1190
+ "name": "state",
1191
+ "isMut": false,
1192
+ "isSigner": false
1193
+ },
1194
+ {
1195
+ "name": "authority",
1196
+ "isMut": false,
1197
+ "isSigner": true
1198
+ },
1199
+ {
1200
+ "name": "rent",
1201
+ "isMut": false,
1202
+ "isSigner": false
1203
+ }
1204
+ ],
1205
+ "args": []
1206
+ },
1176
1207
  {
1177
1208
  "name": "fillPerpOrder",
1178
1209
  "accounts": [
@@ -7343,12 +7374,24 @@
7343
7374
  "name": "totalFeeEarnedPerLp",
7344
7375
  "type": "u64"
7345
7376
  },
7377
+ {
7378
+ "name": "netUnsettledFundingPnl",
7379
+ "type": "i64"
7380
+ },
7381
+ {
7382
+ "name": "quoteAssetAmountWithUnsettledLp",
7383
+ "type": "i64"
7384
+ },
7385
+ {
7386
+ "name": "referencePriceOffset",
7387
+ "type": "i32"
7388
+ },
7346
7389
  {
7347
7390
  "name": "padding",
7348
7391
  "type": {
7349
7392
  "array": [
7350
7393
  "u8",
7351
- 32
7394
+ 12
7352
7395
  ]
7353
7396
  }
7354
7397
  }
@@ -8232,6 +8275,9 @@
8232
8275
  },
8233
8276
  {
8234
8277
  "name": "Transfer"
8278
+ },
8279
+ {
8280
+ "name": "Borrow"
8235
8281
  }
8236
8282
  ]
8237
8283
  }
@@ -11124,6 +11170,11 @@
11124
11170
  "code": 6256,
11125
11171
  "name": "CantPayUserInitFee",
11126
11172
  "msg": "CantPayUserInitFee"
11173
+ },
11174
+ {
11175
+ "code": 6257,
11176
+ "name": "CantReclaimRent",
11177
+ "msg": "CantReclaimRent"
11127
11178
  }
11128
11179
  ]
11129
11180
  }
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export * from './accounts/pollingUserAccountSubscriber';
21
21
  export * from './accounts/pollingUserStatsAccountSubscriber';
22
22
  export * from './accounts/pollingInsuranceFundStakeAccountSubscriber';
23
23
  export * from './accounts/basicUserAccountSubscriber';
24
+ export * from './accounts/oneShotUserAccountSubscriber';
24
25
  export * from './accounts/types';
25
26
  export * from './addresses/pda';
26
27
  export * from './adminClient';
@@ -68,7 +69,7 @@ export * from './constants/numericConstants';
68
69
  export * from './serum/serumSubscriber';
69
70
  export * from './serum/serumFulfillmentConfigMap';
70
71
  export * from './phoenix/phoenixSubscriber';
71
- export * from './priorityFee/priorityFeeSubscriber';
72
+ export * from './priorityFee';
72
73
  export * from './phoenix/phoenixFulfillmentConfigMap';
73
74
  export * from './tx/fastSingleTxSender';
74
75
  export * from './tx/retryTxSender';
package/src/math/amm.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  PRICE_DIV_PEG,
13
13
  PERCENTAGE_PRECISION,
14
14
  DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
15
+ FUNDING_RATE_BUFFER_PRECISION,
15
16
  TWO,
16
17
  } from '../constants/numericConstants';
17
18
  import {
@@ -22,7 +23,7 @@ import {
22
23
  isVariant,
23
24
  } from '../types';
24
25
  import { assert } from '../assert/assert';
25
- import { squareRootBN, clampBN, standardizeBaseAssetAmount } from '..';
26
+ import { squareRootBN, sigNum, clampBN, standardizeBaseAssetAmount } from '..';
26
27
 
27
28
  import { OraclePriceData } from '../oracles/types';
28
29
  import {
@@ -352,21 +353,12 @@ export function calculateMarketOpenBidAsk(
352
353
  return [openBids, openAsks];
353
354
  }
354
355
 
355
- export function calculateInventoryScale(
356
+ export function calculateInventoryLiquidityRatio(
356
357
  baseAssetAmountWithAmm: BN,
357
358
  baseAssetReserve: BN,
358
359
  minBaseAssetReserve: BN,
359
- maxBaseAssetReserve: BN,
360
- directionalSpread: number,
361
- maxSpread: number
362
- ): number {
363
- if (baseAssetAmountWithAmm.eq(ZERO)) {
364
- return 1;
365
- }
366
-
367
- const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
368
- new BN(10)
369
- );
360
+ maxBaseAssetReserve: BN
361
+ ): BN {
370
362
  // inventory skew
371
363
  const [openBids, openAsks] = calculateMarketOpenBidAsk(
372
364
  baseAssetReserve,
@@ -383,6 +375,31 @@ export function calculateInventoryScale(
383
375
  .abs(),
384
376
  PERCENTAGE_PRECISION
385
377
  );
378
+ return inventoryScaleBN;
379
+ }
380
+
381
+ export function calculateInventoryScale(
382
+ baseAssetAmountWithAmm: BN,
383
+ baseAssetReserve: BN,
384
+ minBaseAssetReserve: BN,
385
+ maxBaseAssetReserve: BN,
386
+ directionalSpread: number,
387
+ maxSpread: number
388
+ ): number {
389
+ if (baseAssetAmountWithAmm.eq(ZERO)) {
390
+ return 1;
391
+ }
392
+
393
+ const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
394
+ new BN(10)
395
+ );
396
+
397
+ const inventoryScaleBN = calculateInventoryLiquidityRatio(
398
+ baseAssetAmountWithAmm,
399
+ baseAssetReserve,
400
+ minBaseAssetReserve,
401
+ maxBaseAssetReserve
402
+ );
386
403
 
387
404
  const inventoryScaleMaxBN = BN.max(
388
405
  MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
@@ -402,6 +419,76 @@ export function calculateInventoryScale(
402
419
  return inventoryScaleCapped;
403
420
  }
404
421
 
422
+ export function calculateReferencePriceOffset(
423
+ reservePrice: BN,
424
+ last24hAvgFundingRate: BN,
425
+ liquidityFraction: BN,
426
+ oracleTwapFast: BN,
427
+ markTwapFast: BN,
428
+ oracleTwapSlow: BN,
429
+ markTwapSlow: BN,
430
+ maxOffsetPct: number
431
+ ): BN {
432
+ if (last24hAvgFundingRate.eq(ZERO)) {
433
+ return ZERO;
434
+ }
435
+
436
+ const maxOffsetInPrice = new BN(maxOffsetPct)
437
+ .mul(reservePrice)
438
+ .div(PERCENTAGE_PRECISION);
439
+
440
+ // Calculate quote denominated market premium
441
+ const markPremiumMinute = clampBN(
442
+ markTwapFast.sub(oracleTwapFast),
443
+ maxOffsetInPrice.mul(new BN(-1)),
444
+ maxOffsetInPrice
445
+ );
446
+
447
+ const markPremiumHour = clampBN(
448
+ markTwapSlow.sub(oracleTwapSlow),
449
+ maxOffsetInPrice.mul(new BN(-1)),
450
+ maxOffsetInPrice
451
+ );
452
+
453
+ // Convert last24hAvgFundingRate to quote denominated premium
454
+ const markPremiumDay = clampBN(
455
+ last24hAvgFundingRate.div(FUNDING_RATE_BUFFER_PRECISION).mul(new BN(24)),
456
+ maxOffsetInPrice.mul(new BN(-1)),
457
+ maxOffsetInPrice
458
+ );
459
+
460
+ // Take average clamped premium as the price-based offset
461
+ const markPremiumAvg = markPremiumMinute
462
+ .add(markPremiumHour)
463
+ .add(markPremiumDay)
464
+ .div(new BN(3));
465
+
466
+ const markPremiumAvgPct = markPremiumAvg
467
+ .mul(PRICE_PRECISION)
468
+ .div(reservePrice);
469
+
470
+ const inventoryPct = clampBN(
471
+ liquidityFraction.mul(new BN(maxOffsetPct)).div(PERCENTAGE_PRECISION),
472
+ maxOffsetInPrice.mul(new BN(-1)),
473
+ maxOffsetInPrice
474
+ );
475
+
476
+ // Only apply when inventory is consistent with recent and 24h market premium
477
+ let offsetPct = markPremiumAvgPct.add(inventoryPct);
478
+
479
+ if (!sigNum(inventoryPct).eq(sigNum(markPremiumAvgPct))) {
480
+ offsetPct = ZERO;
481
+ }
482
+
483
+ const clampedOffsetPct = clampBN(
484
+ offsetPct,
485
+ new BN(-maxOffsetPct),
486
+ new BN(maxOffsetPct)
487
+ );
488
+
489
+ return clampedOffsetPct;
490
+ }
491
+
405
492
  export function calculateEffectiveLeverage(
406
493
  baseSpread: number,
407
494
  quoteAssetReserve: BN,
@@ -532,6 +619,8 @@ export function calculateSpreadBN(
532
619
  halfRevenueRetreatAmount: 0,
533
620
  longSpreadwRevRetreat: 0,
534
621
  shortSpreadwRevRetreat: 0,
622
+ longSpreadwOffsetShrink: 0,
623
+ shortSpreadwOffsetShrink: 0,
535
624
  totalSpread: 0,
536
625
  longSpread: 0,
537
626
  shortSpread: 0,
@@ -678,7 +767,6 @@ export function calculateSpreadBN(
678
767
  spreadTerms.totalSpread = totalSpread;
679
768
  spreadTerms.longSpread = longSpread;
680
769
  spreadTerms.shortSpread = shortSpread;
681
-
682
770
  if (returnTerms) {
683
771
  return spreadTerms;
684
772
  }
@@ -688,17 +776,20 @@ export function calculateSpreadBN(
688
776
  export function calculateSpread(
689
777
  amm: AMM,
690
778
  oraclePriceData: OraclePriceData,
691
- now?: BN
779
+ now?: BN,
780
+ reservePrice?: BN
692
781
  ): [number, number] {
693
782
  if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
694
783
  return [amm.baseSpread / 2, amm.baseSpread / 2];
695
784
  }
696
785
 
697
- const reservePrice = calculatePrice(
698
- amm.baseAssetReserve,
699
- amm.quoteAssetReserve,
700
- amm.pegMultiplier
701
- );
786
+ if (!reservePrice) {
787
+ reservePrice = calculatePrice(
788
+ amm.baseAssetReserve,
789
+ amm.quoteAssetReserve,
790
+ amm.pegMultiplier
791
+ );
792
+ }
702
793
 
703
794
  const targetPrice = oraclePriceData?.price || reservePrice;
704
795
  const confInterval = oraclePriceData.confidence || ZERO;
@@ -760,13 +851,22 @@ export function calculateSpreadReserves(
760
851
  quoteAssetReserve: amm.quoteAssetReserve,
761
852
  };
762
853
  }
763
- const spreadFraction = BN.max(new BN(spread / 2), ONE);
854
+ let spreadFraction = new BN(spread / 2);
855
+
856
+ // make non-zero
857
+ if (spreadFraction.eq(ZERO)) {
858
+ spreadFraction = spread >= 0 ? new BN(1) : new BN(-1);
859
+ }
860
+
764
861
  const quoteAssetReserveDelta = amm.quoteAssetReserve.div(
765
862
  BID_ASK_SPREAD_PRECISION.div(spreadFraction)
766
863
  );
767
864
 
768
865
  let quoteAssetReserve;
769
- if (isVariant(direction, 'long')) {
866
+ if (
867
+ (spread >= 0 && isVariant(direction, 'long')) ||
868
+ (spread <= 0 && isVariant(direction, 'short'))
869
+ ) {
770
870
  quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
771
871
  } else {
772
872
  quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
@@ -779,14 +879,48 @@ export function calculateSpreadReserves(
779
879
  };
780
880
  }
781
881
 
782
- const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now);
882
+ const reservePrice = calculatePrice(
883
+ amm.baseAssetReserve,
884
+ amm.quoteAssetReserve,
885
+ amm.pegMultiplier
886
+ );
887
+
888
+ // always allow 10 bps of price offset, up to a fifth of the market's max_spread
889
+ const maxOffset = Math.max(
890
+ amm.maxSpread / 5,
891
+ PERCENTAGE_PRECISION.toNumber() / 1000
892
+ );
893
+ const liquidityFraction = calculateInventoryLiquidityRatio(
894
+ amm.baseAssetAmountWithAmm,
895
+ amm.baseAssetReserve,
896
+ amm.minBaseAssetReserve,
897
+ amm.maxBaseAssetReserve
898
+ );
899
+ const referencePriceOffset = calculateReferencePriceOffset(
900
+ reservePrice,
901
+ amm.last24HAvgFundingRate,
902
+ liquidityFraction,
903
+ amm.historicalOracleData.lastOraclePriceTwap5Min,
904
+ amm.lastMarkPriceTwap5Min,
905
+ amm.historicalOracleData.lastOraclePriceTwap,
906
+ amm.lastMarkPriceTwap,
907
+ maxOffset
908
+ );
909
+
910
+ const [longSpread, shortSpread] = calculateSpread(
911
+ amm,
912
+ oraclePriceData,
913
+ now,
914
+ reservePrice
915
+ );
916
+
783
917
  const askReserves = calculateSpreadReserve(
784
- longSpread,
918
+ longSpread + referencePriceOffset.toNumber(),
785
919
  PositionDirection.LONG,
786
920
  amm
787
921
  );
788
922
  const bidReserves = calculateSpreadReserve(
789
- shortSpread,
923
+ shortSpread + referencePriceOffset.toNumber(),
790
924
  PositionDirection.SHORT,
791
925
  amm
792
926
  );
package/src/math/state.ts CHANGED
@@ -22,5 +22,8 @@ export function calculateInitUserFee(stateAccount: StateAccount): BN {
22
22
  }
23
23
 
24
24
  export function getMaxNumberOfSubAccounts(stateAccount: StateAccount): BN {
25
+ if (stateAccount.maxNumberOfSubAccounts <= 5) {
26
+ return new BN(stateAccount.maxNumberOfSubAccounts);
27
+ }
25
28
  return new BN(stateAccount.maxNumberOfSubAccounts).muln(100);
26
29
  }
@@ -39,6 +39,7 @@ export class OrderSubscriber {
39
39
  commitment: this.commitment,
40
40
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
41
41
  resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
42
+ resyncIntervalMs: config.subscriptionConfig.resyncIntervalMs,
42
43
  });
43
44
  }
44
45
  if (config.fastDecode ?? true) {