@drift-labs/sdk 2.52.0-beta.9 → 2.53.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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.52.0-beta.9
1
+ 2.53.0-beta.1
@@ -1,38 +1,57 @@
1
1
  import { Connection, PublicKey } from '@solana/web3.js';
2
- import { BASE_PRECISION, PRICE_PRECISION, PhoenixSubscriber } from '../src';
2
+ import {
3
+ BASE_PRECISION,
4
+ L2Level,
5
+ PRICE_PRECISION,
6
+ PhoenixSubscriber,
7
+ } from '../src';
3
8
  import { PROGRAM_ID } from '@ellipsis-labs/phoenix-sdk';
4
9
 
5
10
  export async function listenToBook(): Promise<void> {
6
11
  const connection = new Connection('https://api.mainnet-beta.solana.com');
7
12
 
8
- const phoenixSubscriber = new PhoenixSubscriber({
9
- connection,
10
- programId: PROGRAM_ID,
11
- marketAddress: new PublicKey(
12
- '4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg'
13
- ),
14
- accountSubscription: {
15
- type: 'websocket',
16
- },
17
- });
18
-
19
- await phoenixSubscriber.subscribe();
20
-
21
- for (let i = 0; i < 10; i++) {
22
- const bids = phoenixSubscriber.getL2Levels("bids");
23
- const asks = phoenixSubscriber.getL2Levels("asks");
24
- console.log("bids");
25
- for (const bid of bids) {
26
- console.log(bid.price.toNumber() / PRICE_PRECISION.toNumber(), bid.size.toNumber() / BASE_PRECISION.toNumber());
13
+ for (const market of [
14
+ '4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg', // SOL/USDC
15
+ 'Ew3vFDdtdGrknJAVVfraxCA37uNJtimXYPY4QjnfhFHH', // ETH/USDC
16
+ '2sTMN9A1D1qeZLF95XQgJCUPiKe5DiV52jLfZGqMP46m', // PYTH/USDC
17
+ 'BRLLmdtPGuuFn3BU6orYw4KHaohAEptBToi3dwRUnHQZ', // JTO/USDC
18
+ ]) {
19
+ const phoenixSubscriber = new PhoenixSubscriber({
20
+ connection,
21
+ programId: PROGRAM_ID,
22
+ marketAddress: new PublicKey(market),
23
+ accountSubscription: {
24
+ type: 'websocket',
25
+ },
26
+ });
27
+
28
+ await phoenixSubscriber.subscribe();
29
+
30
+ const bids = phoenixSubscriber.getL2Levels('bids');
31
+ const asks = phoenixSubscriber.getL2Levels('asks');
32
+ let bid: L2Level | null = null;
33
+ for (const b of bids) {
34
+ bid = b;
35
+ break;
27
36
  }
28
- console.log("asks");
29
- for (const ask of asks) {
30
- console.log(ask.price.toNumber() / PRICE_PRECISION.toNumber(), ask.size.toNumber() / BASE_PRECISION.toNumber());
37
+ let ask: L2Level | null = null;
38
+ for (const a of asks) {
39
+ ask = a;
40
+ break;
31
41
  }
32
- await new Promise((r) => setTimeout(r, 2000));
33
- }
34
42
 
35
- await phoenixSubscriber.unsubscribe();
43
+ console.log('market', market);
44
+ console.log(
45
+ (bid?.size.toNumber() || 0) / BASE_PRECISION.toNumber(),
46
+ (bid?.price.toNumber() || 0) / PRICE_PRECISION.toNumber(),
47
+ '@',
48
+ (ask?.price.toNumber() || (1 << 53) - 1) / PRICE_PRECISION.toNumber(),
49
+ (ask?.size.toNumber() || 0) / BASE_PRECISION.toNumber()
50
+ );
51
+ console.log();
52
+
53
+ await phoenixSubscriber.unsubscribe();
54
+ }
36
55
  }
37
56
 
38
57
  (async function () {
@@ -214,6 +214,16 @@ exports.DevnetPerpMarkets = [
214
214
  launchTs: 1701967240000,
215
215
  oracleSource: __1.OracleSource.PYTH,
216
216
  },
217
+ {
218
+ fullName: 'SEI',
219
+ category: ['L1'],
220
+ symbol: 'SEI-PERP',
221
+ baseAssetSymbol: 'SEI',
222
+ marketIndex: 21,
223
+ oracle: new web3_js_1.PublicKey('B6KVbgqTRY33yDgjAnc1mWw4ATS4W5544xghayQscdt7'),
224
+ launchTs: 1703173331000,
225
+ oracleSource: __1.OracleSource.PYTH,
226
+ },
217
227
  ];
218
228
  exports.MainnetPerpMarkets = [
219
229
  {
@@ -426,6 +436,16 @@ exports.MainnetPerpMarkets = [
426
436
  launchTs: 1701967240000,
427
437
  oracleSource: __1.OracleSource.PYTH,
428
438
  },
439
+ {
440
+ fullName: 'SEI',
441
+ category: ['L1'],
442
+ symbol: 'SEI-PERP',
443
+ baseAssetSymbol: 'SEI',
444
+ marketIndex: 21,
445
+ oracle: new web3_js_1.PublicKey('6cUuAyAX3eXoiWkjFF77RQBEUF15AAMQ7d1hm4EPd3tv'),
446
+ launchTs: 1703173331000,
447
+ oracleSource: __1.OracleSource.PYTH,
448
+ },
429
449
  ];
430
450
  exports.PerpMarkets = {
431
451
  devnet: exports.DevnetPerpMarkets,
@@ -133,8 +133,10 @@ export declare class DriftClient {
133
133
  getUserAccountsForAuthority(authority: PublicKey): Promise<UserAccount[]>;
134
134
  getReferredUserStatsAccountsByReferrer(referrer: PublicKey): Promise<UserStatsAccount[]>;
135
135
  getReferrerNameAccountsForAuthority(authority: PublicKey): Promise<ReferrerNameAccount[]>;
136
- getUserDeletionIx(userAccountPublicKey: PublicKey): Promise<anchor.web3.TransactionInstruction>;
137
136
  deleteUser(subAccountId?: number, txParams?: TxParams): Promise<TransactionSignature>;
137
+ getUserDeletionIx(userAccountPublicKey: PublicKey): Promise<anchor.web3.TransactionInstruction>;
138
+ reclaimRent(subAccountId?: number, txParams?: TxParams): Promise<TransactionSignature>;
139
+ getReclaimRentIx(userAccountPublicKey: PublicKey): Promise<anchor.web3.TransactionInstruction>;
138
140
  getUser(subAccountId?: number, authority?: PublicKey): User;
139
141
  hasUser(subAccountId?: number, authority?: PublicKey): boolean;
140
142
  getUsers(): User[];
@@ -665,6 +665,16 @@ class DriftClient {
665
665
  ]);
666
666
  return programAccounts.map((programAccount) => programAccount.account);
667
667
  }
668
+ async deleteUser(subAccountId = 0, txParams) {
669
+ var _a;
670
+ const userAccountPublicKey = (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, this.wallet.publicKey, subAccountId);
671
+ const ix = await this.getUserDeletionIx(userAccountPublicKey);
672
+ const { txSig } = await this.sendTransaction(await this.buildTransaction(ix, txParams), [], this.opts);
673
+ const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
674
+ await ((_a = this.users.get(userMapKey)) === null || _a === void 0 ? void 0 : _a.unsubscribe());
675
+ this.users.delete(userMapKey);
676
+ return txSig;
677
+ }
668
678
  async getUserDeletionIx(userAccountPublicKey) {
669
679
  const ix = await this.program.instruction.deleteUser({
670
680
  accounts: {
@@ -676,16 +686,23 @@ class DriftClient {
676
686
  });
677
687
  return ix;
678
688
  }
679
- async deleteUser(subAccountId = 0, txParams) {
680
- var _a;
689
+ async reclaimRent(subAccountId = 0, txParams) {
681
690
  const userAccountPublicKey = (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, this.wallet.publicKey, subAccountId);
682
- const ix = await this.getUserDeletionIx(userAccountPublicKey);
691
+ const ix = await this.getReclaimRentIx(userAccountPublicKey);
683
692
  const { txSig } = await this.sendTransaction(await this.buildTransaction(ix, txParams), [], this.opts);
684
- const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
685
- await ((_a = this.users.get(userMapKey)) === null || _a === void 0 ? void 0 : _a.unsubscribe());
686
- this.users.delete(userMapKey);
687
693
  return txSig;
688
694
  }
695
+ async getReclaimRentIx(userAccountPublicKey) {
696
+ return await this.program.instruction.reclaimRent({
697
+ accounts: {
698
+ user: userAccountPublicKey,
699
+ userStats: this.getUserStatsAccountPublicKey(),
700
+ authority: this.wallet.publicKey,
701
+ state: await this.getStatePublicKey(),
702
+ rent: anchor.web3.SYSVAR_RENT_PUBKEY,
703
+ },
704
+ });
705
+ }
689
706
  getUser(subAccountId, authority) {
690
707
  subAccountId = subAccountId !== null && subAccountId !== void 0 ? subAccountId : this.activeSubAccountId;
691
708
  authority = authority !== null && authority !== void 0 ? authority : this.authority;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.51.0",
2
+ "version": "2.52.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -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": [
@@ -11139,6 +11170,11 @@
11139
11170
  "code": 6256,
11140
11171
  "name": "CantPayUserInitFee",
11141
11172
  "msg": "CantPayUserInitFee"
11173
+ },
11174
+ {
11175
+ "code": 6257,
11176
+ "name": "CantReclaimRent",
11177
+ "msg": "CantReclaimRent"
11142
11178
  }
11143
11179
  ]
11144
11180
  }
package/lib/math/amm.js CHANGED
@@ -456,12 +456,11 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
456
456
  }
457
457
  const quoteAssetReserveDelta = amm.quoteAssetReserve.div(numericConstants_1.BID_ASK_SPREAD_PRECISION.div(spreadFraction));
458
458
  let quoteAssetReserve;
459
- if ((spread >= 0 && (0, types_1.isVariant)(direction, 'long')) ||
460
- (spread <= 0 && (0, types_1.isVariant)(direction, 'short'))) {
461
- quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
459
+ if (quoteAssetReserveDelta.gte(numericConstants_1.ZERO)) {
460
+ quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta.abs());
462
461
  }
463
462
  else {
464
- quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
463
+ quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta.abs());
465
464
  }
466
465
  const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve);
467
466
  return {
@@ -471,12 +470,18 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
471
470
  }
472
471
  const reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
473
472
  // always allow 10 bps of price offset, up to a fifth of the market's max_spread
474
- const maxOffset = Math.max(amm.maxSpread / 5, numericConstants_1.PERCENTAGE_PRECISION.toNumber() / 1000);
475
- const liquidityFraction = calculateInventoryLiquidityRatio(amm.baseAssetAmountWithAmm, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve);
476
- const referencePriceOffset = calculateReferencePriceOffset(reservePrice, amm.last24HAvgFundingRate, liquidityFraction, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, amm.lastMarkPriceTwap, maxOffset);
473
+ let maxOffset = 0;
474
+ let referencePriceOffset = numericConstants_1.ZERO;
475
+ if (amm.curveUpdateIntensity > 100) {
476
+ maxOffset = Math.max(amm.maxSpread / 5, (numericConstants_1.PERCENTAGE_PRECISION.toNumber() / 10000) *
477
+ (amm.curveUpdateIntensity - 100));
478
+ const liquidityFraction = calculateInventoryLiquidityRatio(amm.baseAssetAmountWithAmm, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve);
479
+ const liquidityFractionSigned = liquidityFraction.mul((0, __1.sigNum)(amm.baseAssetAmountWithAmm.add(amm.baseAssetAmountWithUnsettledLp)));
480
+ referencePriceOffset = calculateReferencePriceOffset(reservePrice, amm.last24HAvgFundingRate, liquidityFractionSigned, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, amm.lastMarkPriceTwap, maxOffset);
481
+ }
477
482
  const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now, reservePrice);
478
483
  const askReserves = calculateSpreadReserve(longSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.LONG, amm);
479
- const bidReserves = calculateSpreadReserve(shortSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.SHORT, amm);
484
+ const bidReserves = calculateSpreadReserve(-shortSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.SHORT, amm);
480
485
  return [bidReserves, askReserves];
481
486
  }
482
487
  exports.calculateSpreadReserves = calculateSpreadReserves;
@@ -11,7 +11,7 @@ export declare function standardizePrice(price: BN, tickSize: BN, direction: Pos
11
11
  export declare function getLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number, fallbackPrice?: BN): BN | undefined;
12
12
  export declare function hasLimitPrice(order: Order, slot: number): boolean;
13
13
  export declare function hasAuctionPrice(order: Order, slot: number): boolean;
14
- export declare function isFillableByVAMM(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number, ts: number): boolean;
14
+ export declare function isFillableByVAMM(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number, ts: number, minAuctionDuration: number): boolean;
15
15
  export declare function calculateBaseAssetAmountForAmmToFulfill(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number): BN;
16
16
  export declare function calculateBaseAssetAmountToFillUpToLimitPrice(order: Order, amm: AMM, limitPrice: BN, oraclePriceData: OraclePriceData): BN;
17
17
  export declare function isOrderExpired(order: Order, ts: number, enforceBuffer?: boolean): boolean;
@@ -124,8 +124,8 @@ function hasAuctionPrice(order, slot) {
124
124
  (!order.auctionStartPrice.eq(numericConstants_1.ZERO) || !order.auctionEndPrice.eq(numericConstants_1.ZERO)));
125
125
  }
126
126
  exports.hasAuctionPrice = hasAuctionPrice;
127
- function isFillableByVAMM(order, market, oraclePriceData, slot, ts) {
128
- return (((0, auction_1.isAuctionComplete)(order, slot) &&
127
+ function isFillableByVAMM(order, market, oraclePriceData, slot, ts, minAuctionDuration) {
128
+ return (((0, auction_1.isFallbackAvailableLiquiditySource)(order, minAuctionDuration, slot) &&
129
129
  calculateBaseAssetAmountForAmmToFulfill(order, market, oraclePriceData, slot).gte(market.amm.minOrderSize)) ||
130
130
  isOrderExpired(order, ts));
131
131
  }
package/lib/math/state.js CHANGED
@@ -22,6 +22,9 @@ function calculateInitUserFee(stateAccount) {
22
22
  }
23
23
  exports.calculateInitUserFee = calculateInitUserFee;
24
24
  function getMaxNumberOfSubAccounts(stateAccount) {
25
+ if (stateAccount.maxNumberOfSubAccounts <= 5) {
26
+ return new __1.BN(stateAccount.maxNumberOfSubAccounts);
27
+ }
25
28
  return new __1.BN(stateAccount.maxNumberOfSubAccounts).muln(100);
26
29
  }
27
30
  exports.getMaxNumberOfSubAccounts = getMaxNumberOfSubAccounts;
@@ -99,23 +99,25 @@ class PhoenixSubscriber {
99
99
  return this.getL2Levels('asks');
100
100
  }
101
101
  *getL2Levels(side) {
102
- const tickSize = this.market.data.header
103
- .tickSizeInQuoteAtomsPerBaseUnit;
104
- const baseLotsToRawBaseUnits = this.market.baseLotsToRawBaseUnits(1);
105
- const basePrecision = new anchor_1.BN(Math.pow(10, this.market.data.header.baseParams.decimals) *
106
- baseLotsToRawBaseUnits);
107
- const pricePrecision = numericConstants_1.PRICE_PRECISION.div(tickSize);
108
- const ladder = (0, phoenix_sdk_1.getMarketLadder)(this.market, this.lastSlot, this.lastUnixTimestamp, 20);
102
+ const basePrecision = Math.pow(10, this.market.data.header.baseParams.decimals);
103
+ const pricePrecision = numericConstants_1.PRICE_PRECISION.toNumber();
104
+ const ladder = (0, phoenix_sdk_1.getMarketUiLadder)(this.market, this.lastSlot, this.lastUnixTimestamp, 20);
109
105
  for (let i = 0; i < ladder[side].length; i++) {
110
- const { priceInTicks, sizeInBaseLots } = ladder[side][i];
111
- const size = sizeInBaseLots.mul(basePrecision);
112
- yield {
113
- price: priceInTicks.mul(new anchor_1.BN(pricePrecision)),
114
- size,
115
- sources: {
116
- phoenix: size,
117
- },
118
- };
106
+ const { price, quantity } = ladder[side][i];
107
+ try {
108
+ const size = new anchor_1.BN(quantity * basePrecision);
109
+ const updatedPrice = new anchor_1.BN(price * pricePrecision);
110
+ yield {
111
+ price: updatedPrice,
112
+ size,
113
+ sources: {
114
+ phoenix: size,
115
+ },
116
+ };
117
+ }
118
+ catch {
119
+ continue;
120
+ }
119
121
  }
120
122
  }
121
123
  async unsubscribe() {
@@ -25,16 +25,16 @@ class PriorityFeeSubscriber {
25
25
  }
26
26
  }
27
27
  get avgPriorityFee() {
28
- return this.lastAvgStrategyResult;
28
+ return Math.floor(this.lastAvgStrategyResult);
29
29
  }
30
30
  get maxPriorityFee() {
31
- return this.lastMaxStrategyResult;
31
+ return Math.floor(this.lastMaxStrategyResult);
32
32
  }
33
33
  get customPriorityFee() {
34
34
  if (!this.customStrategy) {
35
35
  console.error('Custom strategy not set');
36
36
  }
37
- return this.lastCustomStrategyResult;
37
+ return Math.floor(this.lastCustomStrategyResult);
38
38
  }
39
39
  async subscribe() {
40
40
  if (this.intervalId) {
@@ -18,6 +18,7 @@ export declare class UserMap implements UserMapInterface {
18
18
  private connection;
19
19
  private commitment;
20
20
  private includeIdle;
21
+ private disableSyncOnTotalAccountsChange;
21
22
  private lastNumberOfSubAccounts;
22
23
  private subscription;
23
24
  private stateAccountUpdateCallback;
@@ -13,7 +13,7 @@ class UserMap {
13
13
  * Constructs a new UserMap instance.
14
14
  */
15
15
  constructor(config) {
16
- var _a, _b, _c;
16
+ var _a, _b, _c, _d;
17
17
  this.userMap = new Map();
18
18
  this.stateAccountUpdateCallback = async (state) => {
19
19
  if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
@@ -32,8 +32,10 @@ class UserMap {
32
32
  this.commitment =
33
33
  (_a = config.subscriptionConfig.commitment) !== null && _a !== void 0 ? _a : this.driftClient.opts.commitment;
34
34
  this.includeIdle = (_b = config.includeIdle) !== null && _b !== void 0 ? _b : false;
35
+ this.disableSyncOnTotalAccountsChange =
36
+ (_c = config.disableSyncOnTotalAccountsChange) !== null && _c !== void 0 ? _c : false;
35
37
  let decodeFn;
36
- if ((_c = config.fastDecode) !== null && _c !== void 0 ? _c : true) {
38
+ if ((_d = config.fastDecode) !== null && _d !== void 0 ? _d : true) {
37
39
  decodeFn = (name, buffer) => (0, user_1.decodeUser)(buffer);
38
40
  }
39
41
  else {
@@ -65,7 +67,9 @@ class UserMap {
65
67
  await this.driftClient.subscribe();
66
68
  this.lastNumberOfSubAccounts =
67
69
  this.driftClient.getStateAccount().numberOfSubAccounts;
68
- this.driftClient.eventEmitter.on('stateAccountUpdate', this.stateAccountUpdateCallback);
70
+ if (!this.disableSyncOnTotalAccountsChange) {
71
+ this.driftClient.eventEmitter.on('stateAccountUpdate', this.stateAccountUpdateCallback);
72
+ }
69
73
  await this.subscription.subscribe();
70
74
  }
71
75
  async addPubkey(userAccountPublicKey, userAccount, slot) {
@@ -260,7 +264,9 @@ class UserMap {
260
264
  this.userMap.delete(key);
261
265
  }
262
266
  if (this.lastNumberOfSubAccounts) {
263
- this.driftClient.eventEmitter.removeListener('stateAccountUpdate', this.stateAccountUpdateCallback);
267
+ if (!this.disableSyncOnTotalAccountsChange) {
268
+ this.driftClient.eventEmitter.removeListener('stateAccountUpdate', this.stateAccountUpdateCallback);
269
+ }
264
270
  this.lastNumberOfSubAccounts = undefined;
265
271
  }
266
272
  }
@@ -18,4 +18,5 @@ export type UserMapConfig = {
18
18
  skipInitialLoad?: boolean;
19
19
  includeIdle?: boolean;
20
20
  fastDecode?: boolean;
21
+ disableSyncOnTotalAccountsChange?: boolean;
21
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.52.0-beta.9",
3
+ "version": "2.53.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -224,6 +224,16 @@ export const DevnetPerpMarkets: PerpMarketConfig[] = [
224
224
  launchTs: 1701967240000,
225
225
  oracleSource: OracleSource.PYTH,
226
226
  },
227
+ {
228
+ fullName: 'SEI',
229
+ category: ['L1'],
230
+ symbol: 'SEI-PERP',
231
+ baseAssetSymbol: 'SEI',
232
+ marketIndex: 21,
233
+ oracle: new PublicKey('B6KVbgqTRY33yDgjAnc1mWw4ATS4W5544xghayQscdt7'),
234
+ launchTs: 1703173331000,
235
+ oracleSource: OracleSource.PYTH,
236
+ },
227
237
  ];
228
238
 
229
239
  export const MainnetPerpMarkets: PerpMarketConfig[] = [
@@ -437,6 +447,16 @@ export const MainnetPerpMarkets: PerpMarketConfig[] = [
437
447
  launchTs: 1701967240000,
438
448
  oracleSource: OracleSource.PYTH,
439
449
  },
450
+ {
451
+ fullName: 'SEI',
452
+ category: ['L1'],
453
+ symbol: 'SEI-PERP',
454
+ baseAssetSymbol: 'SEI',
455
+ marketIndex: 21,
456
+ oracle: new PublicKey('6cUuAyAX3eXoiWkjFF77RQBEUF15AAMQ7d1hm4EPd3tv'),
457
+ launchTs: 1703173331000,
458
+ oracleSource: OracleSource.PYTH,
459
+ },
440
460
  ];
441
461
 
442
462
  export const PerpMarkets: { [key in DriftEnv]: PerpMarketConfig[] } = {
@@ -1112,6 +1112,31 @@ export class DriftClient {
1112
1112
  );
1113
1113
  }
1114
1114
 
1115
+ public async deleteUser(
1116
+ subAccountId = 0,
1117
+ txParams?: TxParams
1118
+ ): Promise<TransactionSignature> {
1119
+ const userAccountPublicKey = getUserAccountPublicKeySync(
1120
+ this.program.programId,
1121
+ this.wallet.publicKey,
1122
+ subAccountId
1123
+ );
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
+
1115
1140
  public async getUserDeletionIx(userAccountPublicKey: PublicKey) {
1116
1141
  const ix = await this.program.instruction.deleteUser({
1117
1142
  accounts: {
@@ -1125,7 +1150,7 @@ export class DriftClient {
1125
1150
  return ix;
1126
1151
  }
1127
1152
 
1128
- public async deleteUser(
1153
+ public async reclaimRent(
1129
1154
  subAccountId = 0,
1130
1155
  txParams?: TxParams
1131
1156
  ): Promise<TransactionSignature> {
@@ -1135,7 +1160,7 @@ export class DriftClient {
1135
1160
  subAccountId
1136
1161
  );
1137
1162
 
1138
- const ix = await this.getUserDeletionIx(userAccountPublicKey);
1163
+ const ix = await this.getReclaimRentIx(userAccountPublicKey);
1139
1164
 
1140
1165
  const { txSig } = await this.sendTransaction(
1141
1166
  await this.buildTransaction(ix, txParams),
@@ -1143,13 +1168,21 @@ export class DriftClient {
1143
1168
  this.opts
1144
1169
  );
1145
1170
 
1146
- const userMapKey = this.getUserMapKey(subAccountId, this.wallet.publicKey);
1147
- await this.users.get(userMapKey)?.unsubscribe();
1148
- this.users.delete(userMapKey);
1149
-
1150
1171
  return txSig;
1151
1172
  }
1152
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
+
1153
1186
  public getUser(subAccountId?: number, authority?: PublicKey): User {
1154
1187
  subAccountId = subAccountId ?? this.activeSubAccountId;
1155
1188
  authority = authority ?? this.authority;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.51.0",
2
+ "version": "2.52.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -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": [
@@ -11139,6 +11170,11 @@
11139
11170
  "code": 6256,
11140
11171
  "name": "CantPayUserInitFee",
11141
11172
  "msg": "CantPayUserInitFee"
11173
+ },
11174
+ {
11175
+ "code": 6257,
11176
+ "name": "CantReclaimRent",
11177
+ "msg": "CantReclaimRent"
11142
11178
  }
11143
11179
  ]
11144
11180
  }
package/src/math/amm.ts CHANGED
@@ -863,13 +863,14 @@ export function calculateSpreadReserves(
863
863
  );
864
864
 
865
865
  let quoteAssetReserve;
866
- if (
867
- (spread >= 0 && isVariant(direction, 'long')) ||
868
- (spread <= 0 && isVariant(direction, 'short'))
869
- ) {
870
- quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
866
+ if (quoteAssetReserveDelta.gte(ZERO)) {
867
+ quoteAssetReserve = amm.quoteAssetReserve.add(
868
+ quoteAssetReserveDelta.abs()
869
+ );
871
870
  } else {
872
- quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
871
+ quoteAssetReserve = amm.quoteAssetReserve.sub(
872
+ quoteAssetReserveDelta.abs()
873
+ );
873
874
  }
874
875
 
875
876
  const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve);
@@ -886,26 +887,35 @@ export function calculateSpreadReserves(
886
887
  );
887
888
 
888
889
  // 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
- );
890
+ let maxOffset = 0;
891
+ let referencePriceOffset = ZERO;
892
+ if (amm.curveUpdateIntensity > 100) {
893
+ maxOffset = Math.max(
894
+ amm.maxSpread / 5,
895
+ (PERCENTAGE_PRECISION.toNumber() / 10000) *
896
+ (amm.curveUpdateIntensity - 100)
897
+ );
898
+
899
+ const liquidityFraction = calculateInventoryLiquidityRatio(
900
+ amm.baseAssetAmountWithAmm,
901
+ amm.baseAssetReserve,
902
+ amm.minBaseAssetReserve,
903
+ amm.maxBaseAssetReserve
904
+ );
905
+ const liquidityFractionSigned = liquidityFraction.mul(
906
+ sigNum(amm.baseAssetAmountWithAmm.add(amm.baseAssetAmountWithUnsettledLp))
907
+ );
908
+ referencePriceOffset = calculateReferencePriceOffset(
909
+ reservePrice,
910
+ amm.last24HAvgFundingRate,
911
+ liquidityFractionSigned,
912
+ amm.historicalOracleData.lastOraclePriceTwap5Min,
913
+ amm.lastMarkPriceTwap5Min,
914
+ amm.historicalOracleData.lastOraclePriceTwap,
915
+ amm.lastMarkPriceTwap,
916
+ maxOffset
917
+ );
918
+ }
909
919
 
910
920
  const [longSpread, shortSpread] = calculateSpread(
911
921
  amm,
@@ -920,7 +930,7 @@ export function calculateSpreadReserves(
920
930
  amm
921
931
  );
922
932
  const bidReserves = calculateSpreadReserve(
923
- shortSpread + referencePriceOffset.toNumber(),
933
+ -shortSpread + referencePriceOffset.toNumber(),
924
934
  PositionDirection.SHORT,
925
935
  amm
926
936
  );
@@ -10,7 +10,11 @@ import {
10
10
  import { ZERO, TWO } from '../constants/numericConstants';
11
11
  import { BN } from '@coral-xyz/anchor';
12
12
  import { OraclePriceData } from '../oracles/types';
13
- import { getAuctionPrice, isAuctionComplete } from './auction';
13
+ import {
14
+ getAuctionPrice,
15
+ isAuctionComplete,
16
+ isFallbackAvailableLiquiditySource,
17
+ } from './auction';
14
18
  import {
15
19
  calculateMaxBaseAssetAmountFillable,
16
20
  calculateMaxBaseAssetAmountToTrade,
@@ -186,10 +190,11 @@ export function isFillableByVAMM(
186
190
  market: PerpMarketAccount,
187
191
  oraclePriceData: OraclePriceData,
188
192
  slot: number,
189
- ts: number
193
+ ts: number,
194
+ minAuctionDuration: number
190
195
  ): boolean {
191
196
  return (
192
- (isAuctionComplete(order, slot) &&
197
+ (isFallbackAvailableLiquiditySource(order, minAuctionDuration, slot) &&
193
198
  calculateBaseAssetAmountForAmmToFulfill(
194
199
  order,
195
200
  market,
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
  }
@@ -6,7 +6,6 @@ import {
6
6
  toNum,
7
7
  getMarketUiLadder,
8
8
  Market,
9
- getMarketLadder,
10
9
  } from '@ellipsis-labs/phoenix-sdk';
11
10
  import { PRICE_PRECISION } from '../constants/numericConstants';
12
11
  import { BN } from '@coral-xyz/anchor';
@@ -164,18 +163,14 @@ export class PhoenixSubscriber implements L2OrderBookGenerator {
164
163
  }
165
164
 
166
165
  *getL2Levels(side: 'bids' | 'asks'): Generator<L2Level> {
167
- const tickSize = this.market.data.header
168
- .tickSizeInQuoteAtomsPerBaseUnit as BN;
169
- const baseLotsToRawBaseUnits = this.market.baseLotsToRawBaseUnits(1);
170
-
171
- const basePrecision = new BN(
172
- Math.pow(10, this.market.data.header.baseParams.decimals) *
173
- baseLotsToRawBaseUnits
166
+ const basePrecision = Math.pow(
167
+ 10,
168
+ this.market.data.header.baseParams.decimals
174
169
  );
175
170
 
176
- const pricePrecision = PRICE_PRECISION.div(tickSize as BN);
171
+ const pricePrecision = PRICE_PRECISION.toNumber();
177
172
 
178
- const ladder = getMarketLadder(
173
+ const ladder = getMarketUiLadder(
179
174
  this.market,
180
175
  this.lastSlot,
181
176
  this.lastUnixTimestamp,
@@ -183,18 +178,22 @@ export class PhoenixSubscriber implements L2OrderBookGenerator {
183
178
  );
184
179
 
185
180
  for (let i = 0; i < ladder[side].length; i++) {
186
- const { priceInTicks, sizeInBaseLots } = ladder[side][i];
187
- const size = sizeInBaseLots.mul(basePrecision);
188
- yield {
189
- price: priceInTicks.mul(new BN(pricePrecision)),
190
- size,
191
- sources: {
192
- phoenix: size,
193
- },
194
- };
181
+ const { price, quantity } = ladder[side][i];
182
+ try {
183
+ const size = new BN(quantity * basePrecision);
184
+ const updatedPrice = new BN(price * pricePrecision);
185
+ yield {
186
+ price: updatedPrice,
187
+ size,
188
+ sources: {
189
+ phoenix: size,
190
+ },
191
+ };
192
+ } catch {
193
+ continue;
194
+ }
195
195
  }
196
196
  }
197
-
198
197
  public async unsubscribe(): Promise<void> {
199
198
  if (!this.subscribed) {
200
199
  return;
@@ -46,18 +46,18 @@ export class PriorityFeeSubscriber {
46
46
  }
47
47
 
48
48
  public get avgPriorityFee(): number {
49
- return this.lastAvgStrategyResult;
49
+ return Math.floor(this.lastAvgStrategyResult);
50
50
  }
51
51
 
52
52
  public get maxPriorityFee(): number {
53
- return this.lastMaxStrategyResult;
53
+ return Math.floor(this.lastMaxStrategyResult);
54
54
  }
55
55
 
56
56
  public get customPriorityFee(): number {
57
57
  if (!this.customStrategy) {
58
58
  console.error('Custom strategy not set');
59
59
  }
60
- return this.lastCustomStrategyResult;
60
+ return Math.floor(this.lastCustomStrategyResult);
61
61
  }
62
62
 
63
63
  public async subscribe(): Promise<void> {
@@ -51,6 +51,7 @@ export class UserMap implements UserMapInterface {
51
51
  private connection: Connection;
52
52
  private commitment: Commitment;
53
53
  private includeIdle: boolean;
54
+ private disableSyncOnTotalAccountsChange: boolean;
54
55
  private lastNumberOfSubAccounts: BN;
55
56
  private subscription: PollingSubscription | WebsocketSubscription;
56
57
  private stateAccountUpdateCallback = async (state: StateAccount) => {
@@ -78,6 +79,8 @@ export class UserMap implements UserMapInterface {
78
79
  this.commitment =
79
80
  config.subscriptionConfig.commitment ?? this.driftClient.opts.commitment;
80
81
  this.includeIdle = config.includeIdle ?? false;
82
+ this.disableSyncOnTotalAccountsChange =
83
+ config.disableSyncOnTotalAccountsChange ?? false;
81
84
 
82
85
  let decodeFn;
83
86
  if (config.fastDecode ?? true) {
@@ -115,10 +118,12 @@ export class UserMap implements UserMapInterface {
115
118
  await this.driftClient.subscribe();
116
119
  this.lastNumberOfSubAccounts =
117
120
  this.driftClient.getStateAccount().numberOfSubAccounts;
118
- this.driftClient.eventEmitter.on(
119
- 'stateAccountUpdate',
120
- this.stateAccountUpdateCallback
121
- );
121
+ if (!this.disableSyncOnTotalAccountsChange) {
122
+ this.driftClient.eventEmitter.on(
123
+ 'stateAccountUpdate',
124
+ this.stateAccountUpdateCallback
125
+ );
126
+ }
122
127
 
123
128
  await this.subscription.subscribe();
124
129
  }
@@ -363,10 +368,13 @@ export class UserMap implements UserMapInterface {
363
368
  }
364
369
 
365
370
  if (this.lastNumberOfSubAccounts) {
366
- this.driftClient.eventEmitter.removeListener(
367
- 'stateAccountUpdate',
368
- this.stateAccountUpdateCallback
369
- );
371
+ if (!this.disableSyncOnTotalAccountsChange) {
372
+ this.driftClient.eventEmitter.removeListener(
373
+ 'stateAccountUpdate',
374
+ this.stateAccountUpdateCallback
375
+ );
376
+ }
377
+
370
378
  this.lastNumberOfSubAccounts = undefined;
371
379
  }
372
380
  }
@@ -31,4 +31,8 @@ export type UserMapConfig = {
31
31
 
32
32
  // Whether to skip loading available perp/spot positions and open orders
33
33
  fastDecode?: boolean;
34
+
35
+ // If true, will not do a full sync whenever StateAccount.numberOfSubAccounts changes.
36
+ // default behavior is to do a full sync on changes.
37
+ disableSyncOnTotalAccountsChange?: boolean;
34
38
  };
package/tests/amm/test.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  calculateSpread,
9
9
  calculateSpreadBN,
10
10
  ZERO,
11
+ sigNum,
11
12
  ONE,
12
13
  calculateLiveOracleStd,
13
14
  calculateLiveOracleTwap,
@@ -465,7 +466,7 @@ describe('AMM Tests', () => {
465
466
 
466
467
  mockAmm.baseAssetAmountWithAmm = new BN(0);
467
468
  mockAmm.pegMultiplier = new BN(13.553 * PEG_PRECISION.toNumber());
468
- mockAmm.ammJitIntensity = 100;
469
+ mockAmm.ammJitIntensity = 200;
469
470
  mockAmm.curveUpdateIntensity = 200;
470
471
  mockAmm.baseSpread = 2500;
471
472
  mockAmm.maxSpread = 25000;
@@ -521,13 +522,13 @@ describe('AMM Tests', () => {
521
522
  console.log('amm.baseAssetReserve:', mockAmm.baseAssetReserve.toString());
522
523
  assert(mockAmm.baseAssetReserve.eq(new BN('1000000000')));
523
524
  const reserves2 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
524
- console.log(reserves2[1].baseAssetReserve.toString());
525
- console.log(reserves2[1].quoteAssetReserve.toString());
525
+ console.log(reserves2[0].baseAssetReserve.toString());
526
+ console.log(reserves2[0].quoteAssetReserve.toString());
526
527
 
527
- assert(reserves2[0].baseAssetReserve.eq(new BN('1006711408')));
528
- assert(reserves2[0].quoteAssetReserve.eq(new BN('993333334')));
529
- assert(reserves2[1].baseAssetReserve.eq(new BN('993377484')));
530
- assert(reserves2[1].quoteAssetReserve.eq(new BN('1006666666')));
528
+ assert(reserves2[0].baseAssetReserve.eq(new BN('1005050504')));
529
+ assert(reserves2[0].quoteAssetReserve.eq(new BN('994974875')));
530
+ assert(reserves2[1].baseAssetReserve.eq(new BN('992537314')));
531
+ assert(reserves2[1].quoteAssetReserve.eq(new BN('1007518796')));
531
532
 
532
533
  // create imbalance for reference price offset
533
534
  mockAmm.baseAssetReserve = new BN(1000000000 * 1.1);
@@ -536,11 +537,12 @@ describe('AMM Tests', () => {
536
537
  mockAmm.baseAssetReserve.mul(mockAmm.quoteAssetReserve)
537
538
  );
538
539
 
539
- mockAmm.baseAssetAmountWithAmm = new BN(-1000000000 * 0.1);
540
+ mockAmm.baseAssetAmountWithAmm = new BN(1000000000 * 0.1);
540
541
 
541
542
  const maxOffset = Math.max(
542
543
  mockAmm.maxSpread / 5,
543
- PERCENTAGE_PRECISION.toNumber() / 1000
544
+ (PERCENTAGE_PRECISION.toNumber() / 10000) *
545
+ (mockAmm.curveUpdateIntensity - 100)
544
546
  );
545
547
  const liquidityFraction = calculateInventoryLiquidityRatio(
546
548
  mockAmm.baseAssetAmountWithAmm,
@@ -550,11 +552,17 @@ describe('AMM Tests', () => {
550
552
  );
551
553
  console.log('liquidityFraction:', liquidityFraction.toString());
552
554
  assert(liquidityFraction.eq(new BN(1000000))); // full
553
-
555
+ const liquidityFractionSigned = liquidityFraction.mul(
556
+ sigNum(
557
+ mockAmm.baseAssetAmountWithAmm.add(
558
+ mockAmm.baseAssetAmountWithUnsettledLp
559
+ )
560
+ )
561
+ );
554
562
  const referencePriceOffset = calculateReferencePriceOffset(
555
563
  reservePrice,
556
564
  mockAmm.last24HAvgFundingRate,
557
- liquidityFraction,
565
+ liquidityFractionSigned,
558
566
  mockAmm.historicalOracleData.lastOraclePriceTwap5Min,
559
567
  mockAmm.lastMarkPriceTwap5Min,
560
568
  mockAmm.historicalOracleData.lastOraclePriceTwap,
@@ -562,17 +570,304 @@ describe('AMM Tests', () => {
562
570
  maxOffset
563
571
  );
564
572
  console.log('referencePriceOffset:', referencePriceOffset.toString());
565
- assert(referencePriceOffset.eq(new BN(5000)));
573
+ assert(referencePriceOffset.eq(new BN(10000)));
566
574
  assert(referencePriceOffset.eq(new BN(maxOffset)));
567
575
 
576
+ // mockAmm.curveUpdateIntensity = 100;
577
+ const reserves3 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
578
+ console.log(reserves3[0].baseAssetReserve.toString());
579
+ console.log(reserves3[0].quoteAssetReserve.toString());
580
+
581
+ assert(reserves3[0].baseAssetReserve.eq(new BN('1094581278')));
582
+ assert(reserves3[0].quoteAssetReserve.eq(new BN('913591359')));
583
+ assert(reserves3[1].baseAssetReserve.eq(new BN('989999998')));
584
+ assert(reserves3[1].quoteAssetReserve.eq(new BN('1010101010')));
585
+
586
+ const p1 = calculatePrice(
587
+ reserves3[0].baseAssetReserve,
588
+ reserves3[0].quoteAssetReserve,
589
+ mockAmm.pegMultiplier
590
+ );
591
+
592
+ const p2 = calculatePrice(
593
+ reserves3[1].baseAssetReserve,
594
+ reserves3[1].quoteAssetReserve,
595
+ mockAmm.pegMultiplier
596
+ );
597
+ console.log(p1.toString(), p2.toString());
598
+
599
+ assert(p1.eq(new BN(11312000)));
600
+ assert(p2.eq(new BN(13828180)));
601
+
602
+ mockAmm.curveUpdateIntensity = 110;
603
+ const reserves4 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
604
+ console.log(reserves4[1].baseAssetReserve.toString());
605
+ console.log(reserves4[1].quoteAssetReserve.toString());
606
+
607
+ assert(reserves4[0].baseAssetReserve.eq(new BN('1097323599')));
608
+ assert(reserves4[0].quoteAssetReserve.eq(new BN('911308203')));
609
+ assert(reserves4[1].baseAssetReserve.eq(new BN('989999998')));
610
+ assert(reserves4[1].quoteAssetReserve.eq(new BN('1010101010')));
611
+
612
+ const p1RF = calculatePrice(
613
+ reserves4[0].baseAssetReserve,
614
+ reserves4[0].quoteAssetReserve,
615
+ mockAmm.pegMultiplier
616
+ );
617
+
618
+ const p2RF = calculatePrice(
619
+ reserves4[1].baseAssetReserve,
620
+ reserves4[1].quoteAssetReserve,
621
+ mockAmm.pegMultiplier
622
+ );
623
+ console.log(p1RF.toString(), p2RF.toString());
624
+
625
+ assert(p1RF.eq(new BN(11255531)));
626
+ assert(p2RF.eq(new BN(13828180)));
627
+ // no ref price offset at 100
628
+ mockAmm.curveUpdateIntensity = 100;
629
+ const reserves5 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
630
+ console.log(reserves5[0].baseAssetReserve.toString());
631
+ console.log(reserves5[0].quoteAssetReserve.toString());
632
+
633
+ assert(reserves5[0].baseAssetReserve.eq(new BN('1100068201')));
634
+ assert(reserves5[0].quoteAssetReserve.eq(new BN('909034546')));
635
+ assert(reserves5[1].baseAssetReserve.eq(new BN('989999998')));
636
+ assert(reserves5[1].quoteAssetReserve.eq(new BN('1010101010')));
637
+
638
+ const p1RFNone = calculatePrice(
639
+ reserves5[0].baseAssetReserve,
640
+ reserves5[0].quoteAssetReserve,
641
+ mockAmm.pegMultiplier
642
+ );
643
+
644
+ const p2RFNone = calculatePrice(
645
+ reserves5[1].baseAssetReserve,
646
+ reserves5[1].quoteAssetReserve,
647
+ mockAmm.pegMultiplier
648
+ );
649
+ console.log(p1RFNone.toString(), p2RFNone.toString());
650
+
651
+ assert(p1RFNone.eq(new BN(11199437)));
652
+ assert(p2RFNone.eq(new BN(13828180)));
653
+ assert(p1RF.sub(p1RFNone).eq(new BN(56094)));
654
+ assert(p2RF.sub(p2RFNone).eq(new BN(0))); // todo?
655
+ });
656
+ it('Spread Reserves (with negative offset)', () => {
657
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
658
+ const mockMarket1 = myMockPerpMarkets[0];
659
+ const mockAmm = mockMarket1.amm;
660
+ const now = new BN(new Date().getTime() / 1000); //todo
661
+
662
+ const oraclePriceData = {
663
+ price: new BN(13.553 * PRICE_PRECISION.toNumber()),
664
+ slot: new BN(68 + 1),
665
+ confidence: new BN(1),
666
+ hasSufficientNumberOfDataPoints: true,
667
+ };
668
+
669
+ const reserves = calculateSpreadReserves(mockAmm, oraclePriceData, now);
670
+ assert(reserves[0].baseAssetReserve.eq(new BN('1000000000')));
671
+ assert(reserves[0].quoteAssetReserve.eq(new BN('12000000000')));
672
+ assert(reserves[1].baseAssetReserve.eq(new BN('1000000000')));
673
+ assert(reserves[1].quoteAssetReserve.eq(new BN('12000000000')));
674
+
675
+ mockAmm.baseAssetReserve = new BN(1000000000);
676
+ mockAmm.quoteAssetReserve = new BN(1000000000);
677
+ mockAmm.sqrtK = new BN(1000000000);
678
+
679
+ mockAmm.baseAssetAmountWithAmm = new BN(0);
680
+ mockAmm.pegMultiplier = new BN(13.553 * PEG_PRECISION.toNumber());
681
+ mockAmm.ammJitIntensity = 200;
682
+ mockAmm.curveUpdateIntensity = 200;
683
+ mockAmm.baseSpread = 2500;
684
+ mockAmm.maxSpread = 25000;
685
+
686
+ mockAmm.last24HAvgFundingRate = new BN(-7590328523);
687
+
688
+ mockAmm.lastMarkPriceTwap = new BN(
689
+ (oraclePriceData.price.toNumber() / 1e6 + 0.01) * 1e6
690
+ );
691
+ mockAmm.historicalOracleData.lastOraclePriceTwap = new BN(
692
+ (oraclePriceData.price.toNumber() / 1e6 - 0.015) * 1e6
693
+ );
694
+
695
+ mockAmm.historicalOracleData.lastOraclePriceTwap5Min = new BN(
696
+ (oraclePriceData.price.toNumber() / 1e6 + 0.005) * 1e6
697
+ );
698
+ mockAmm.lastMarkPriceTwap5Min = new BN(
699
+ (oraclePriceData.price.toNumber() / 1e6 - 0.005) * 1e6
700
+ );
701
+
702
+ console.log('starting rr:');
703
+ let reservePrice = undefined;
704
+ if (!reservePrice) {
705
+ reservePrice = calculatePrice(
706
+ mockAmm.baseAssetReserve,
707
+ mockAmm.quoteAssetReserve,
708
+ mockAmm.pegMultiplier
709
+ );
710
+ }
711
+
712
+ const targetPrice = oraclePriceData?.price || reservePrice;
713
+ const confInterval = oraclePriceData.confidence || ZERO;
714
+ const targetMarkSpreadPct = reservePrice
715
+ .sub(targetPrice)
716
+ .mul(BID_ASK_SPREAD_PRECISION)
717
+ .div(reservePrice);
718
+
719
+ const confIntervalPct = confInterval
720
+ .mul(BID_ASK_SPREAD_PRECISION)
721
+ .div(reservePrice);
722
+
723
+ // now = now || new BN(new Date().getTime() / 1000); //todo
724
+ const liveOracleStd = calculateLiveOracleStd(mockAmm, oraclePriceData, now);
725
+ console.log('reservePrice:', reservePrice.toString());
726
+ console.log('targetMarkSpreadPct:', targetMarkSpreadPct.toString());
727
+ console.log('confIntervalPct:', confIntervalPct.toString());
728
+
729
+ console.log('liveOracleStd:', liveOracleStd.toString());
730
+
731
+ const tt = calculateSpread(mockAmm, oraclePriceData, now);
732
+ console.log(tt);
733
+
734
+ console.log('amm.baseAssetReserve:', mockAmm.baseAssetReserve.toString());
735
+ assert(mockAmm.baseAssetReserve.eq(new BN('1000000000')));
736
+ const reserves2 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
737
+ console.log(reserves2[1].baseAssetReserve.toString());
738
+ console.log(reserves2[1].quoteAssetReserve.toString());
739
+
740
+ assert(reserves2[0].baseAssetReserve.eq(new BN('1006289308')));
741
+ assert(reserves2[0].quoteAssetReserve.eq(new BN('993750000')));
742
+ assert(reserves2[1].baseAssetReserve.eq(new BN('993788819')));
743
+ assert(reserves2[1].quoteAssetReserve.eq(new BN('1006250000')));
744
+
745
+ // create imbalance for reference price offset
746
+ mockAmm.baseAssetReserve = new BN(1000000000 / 1.1);
747
+ mockAmm.quoteAssetReserve = new BN(1000000000 * 1.1);
748
+ mockAmm.sqrtK = squareRootBN(
749
+ mockAmm.baseAssetReserve.mul(mockAmm.quoteAssetReserve)
750
+ );
751
+
752
+ mockAmm.baseAssetAmountWithAmm = new BN(-1000000000 * 0.1);
753
+
754
+ const maxOffset = Math.max(
755
+ mockAmm.maxSpread / 5,
756
+ (PERCENTAGE_PRECISION.toNumber() / 10000) *
757
+ (mockAmm.curveUpdateIntensity - 100)
758
+ );
759
+ const liquidityFraction = calculateInventoryLiquidityRatio(
760
+ mockAmm.baseAssetAmountWithAmm,
761
+ mockAmm.baseAssetReserve,
762
+ mockAmm.minBaseAssetReserve,
763
+ mockAmm.maxBaseAssetReserve
764
+ );
765
+ console.log('liquidityFraction:', liquidityFraction.toString());
766
+ assert(liquidityFraction.eq(new BN(1000000))); // full
767
+ const liquidityFractionSigned = liquidityFraction.mul(
768
+ sigNum(
769
+ mockAmm.baseAssetAmountWithAmm.add(
770
+ mockAmm.baseAssetAmountWithUnsettledLp
771
+ )
772
+ )
773
+ );
774
+ const referencePriceOffset = calculateReferencePriceOffset(
775
+ reservePrice,
776
+ mockAmm.last24HAvgFundingRate,
777
+ liquidityFractionSigned,
778
+ mockAmm.historicalOracleData.lastOraclePriceTwap5Min,
779
+ mockAmm.lastMarkPriceTwap5Min,
780
+ mockAmm.historicalOracleData.lastOraclePriceTwap,
781
+ mockAmm.lastMarkPriceTwap,
782
+ maxOffset
783
+ );
784
+ console.log('referencePriceOffset:', referencePriceOffset.toString());
785
+ assert(referencePriceOffset.eq(new BN(-10000))); // neg
786
+
787
+ // assert(referencePriceOffset.eq(new BN(maxOffset)));
788
+
789
+ // mockAmm.curveUpdateIntensity = 100;
568
790
  const reserves3 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
569
- console.log(reserves3[1].baseAssetReserve.toString());
570
- console.log(reserves3[1].quoteAssetReserve.toString());
791
+ console.log(reserves3[0].baseAssetReserve.toString());
792
+ console.log(reserves3[0].quoteAssetReserve.toString());
793
+
794
+ assert(reserves3[0].baseAssetReserve.eq(new BN('1010101008')));
795
+ assert(reserves3[0].quoteAssetReserve.eq(new BN('990000000')));
796
+ assert(reserves3[1].baseAssetReserve.eq(new BN('913613747')));
797
+ assert(reserves3[1].quoteAssetReserve.eq(new BN('1094554456')));
798
+
799
+ const p1 = calculatePrice(
800
+ reserves3[0].baseAssetReserve,
801
+ reserves3[0].quoteAssetReserve,
802
+ mockAmm.pegMultiplier
803
+ );
804
+
805
+ const p2 = calculatePrice(
806
+ reserves3[1].baseAssetReserve,
807
+ reserves3[1].quoteAssetReserve,
808
+ mockAmm.pegMultiplier
809
+ );
810
+ console.log(p1.toString(), p2.toString());
811
+
812
+ assert(p1.eq(new BN(13283295)));
813
+ assert(p2.eq(new BN(16237164)));
814
+
815
+ mockAmm.curveUpdateIntensity = 110;
816
+ const reserves4 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
817
+ console.log(reserves4[1].baseAssetReserve.toString());
818
+ console.log(reserves4[1].quoteAssetReserve.toString());
819
+
820
+ assert(reserves4[0].baseAssetReserve.eq(new BN('999999998')));
821
+ assert(reserves4[0].quoteAssetReserve.eq(new BN('1000000000')));
822
+ assert(reserves4[1].baseAssetReserve.eq(new BN('911313622')));
823
+ assert(reserves4[1].quoteAssetReserve.eq(new BN('1097317074')));
824
+
825
+ const p1RF = calculatePrice(
826
+ reserves4[0].baseAssetReserve,
827
+ reserves4[0].quoteAssetReserve,
828
+ mockAmm.pegMultiplier
829
+ );
830
+
831
+ const p2RF = calculatePrice(
832
+ reserves4[1].baseAssetReserve,
833
+ reserves4[1].quoteAssetReserve,
834
+ mockAmm.pegMultiplier
835
+ );
836
+ console.log(p1RF.toString(), p2RF.toString());
837
+
838
+ assert(p1RF.eq(new BN(13553000)));
839
+ assert(p2RF.eq(new BN(16319231)));
840
+
841
+ // no ref price offset at 100
842
+ mockAmm.curveUpdateIntensity = 100;
843
+ const reserves5 = calculateSpreadReserves(mockAmm, oraclePriceData, now);
844
+ console.log(reserves5[0].baseAssetReserve.toString());
845
+ console.log(reserves5[0].quoteAssetReserve.toString());
846
+
847
+ assert(reserves5[0].baseAssetReserve.eq(new BN('999999998')));
848
+ assert(reserves5[0].quoteAssetReserve.eq(new BN('1000000000')));
849
+ assert(reserves5[1].baseAssetReserve.eq(new BN('909034547')));
850
+ assert(reserves5[1].quoteAssetReserve.eq(new BN('1100068200')));
851
+
852
+ const p1RFNone = calculatePrice(
853
+ reserves5[0].baseAssetReserve,
854
+ reserves5[0].quoteAssetReserve,
855
+ mockAmm.pegMultiplier
856
+ );
857
+
858
+ const p2RFNone = calculatePrice(
859
+ reserves5[1].baseAssetReserve,
860
+ reserves5[1].quoteAssetReserve,
861
+ mockAmm.pegMultiplier
862
+ );
863
+ console.log(p1RFNone.toString(), p2RFNone.toString());
571
864
 
572
- assert(reserves3[0].baseAssetReserve.eq(new BN('1164705879')));
573
- assert(reserves3[0].quoteAssetReserve.eq(new BN('858585859')));
574
- assert(reserves3[1].baseAssetReserve.eq(new BN('1042105261')));
575
- assert(reserves3[1].quoteAssetReserve.eq(new BN('959595959')));
865
+ const rr = p2RF.sub(p2RFNone).mul(PERCENTAGE_PRECISION).div(p2RF);
866
+ console.log(rr.toNumber());
867
+ assert(p1RFNone.eq(new BN(13553000)));
868
+ assert(p2RFNone.eq(new BN(16401163)));
869
+ assert(p1RF.sub(p1RFNone).eq(new BN(0))); // todo?
870
+ assert(rr.eq(new BN(-5020)));
576
871
  });
577
872
 
578
873
  it('live update functions', () => {