@drift-labs/sdk 2.67.0-beta.0 → 2.67.0-beta.2

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.
@@ -0,0 +1,11 @@
1
+ /// <reference types="node" />
2
+ import { Connection, PublicKey } from '@solana/web3.js';
3
+ import { OracleClient, OraclePriceData } from './types';
4
+ import { BorshAccountsCoder } from '@coral-xyz/anchor';
5
+ export declare class SwitchboardClient implements OracleClient {
6
+ connection: Connection;
7
+ coder: BorshAccountsCoder;
8
+ constructor(connection: Connection);
9
+ getOraclePriceData(pricePublicKey: PublicKey): Promise<OraclePriceData>;
10
+ getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData;
11
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SwitchboardClient = void 0;
7
+ const numericConstants_1 = require("../constants/numericConstants");
8
+ const switchboard_json_1 = __importDefault(require("../idl/switchboard.json"));
9
+ const anchor_1 = require("@coral-xyz/anchor");
10
+ class SwitchboardClient {
11
+ constructor(connection) {
12
+ this.connection = connection;
13
+ this.coder = new anchor_1.BorshAccountsCoder(switchboard_json_1.default);
14
+ }
15
+ async getOraclePriceData(pricePublicKey) {
16
+ const accountInfo = await this.connection.getAccountInfo(pricePublicKey);
17
+ return this.getOraclePriceDataFromBuffer(accountInfo.data);
18
+ }
19
+ getOraclePriceDataFromBuffer(buffer) {
20
+ const aggregatorAccountData = this.coder.decodeUnchecked('AggregatorAccountData', buffer);
21
+ const price = convertSwitchboardDecimal(aggregatorAccountData.latestConfirmedRound.result);
22
+ const confidence = convertSwitchboardDecimal(aggregatorAccountData.latestConfirmedRound.stdDeviation);
23
+ const hasSufficientNumberOfDataPoints = aggregatorAccountData.latestConfirmedRound.numSuccess >=
24
+ aggregatorAccountData.minOracleResults;
25
+ const slot = aggregatorAccountData.latestConfirmedRound.roundOpenSlot;
26
+ return {
27
+ price,
28
+ slot,
29
+ confidence,
30
+ hasSufficientNumberOfDataPoints,
31
+ };
32
+ }
33
+ }
34
+ exports.SwitchboardClient = SwitchboardClient;
35
+ function convertSwitchboardDecimal(switchboardDecimal) {
36
+ const switchboardPrecision = numericConstants_1.TEN.pow(new anchor_1.BN(switchboardDecimal.scale));
37
+ return switchboardDecimal.mantissa
38
+ .mul(numericConstants_1.PRICE_PRECISION)
39
+ .div(switchboardPrecision);
40
+ }
package/lib/types.d.ts CHANGED
@@ -143,6 +143,9 @@ export declare class OracleSource {
143
143
  static readonly PYTH_1M: {
144
144
  pyth1M: {};
145
145
  };
146
+ static readonly SWITCHBOARD: {
147
+ switchboard: {};
148
+ };
146
149
  static readonly QUOTE_ASSET: {
147
150
  quoteAsset: {};
148
151
  };
package/lib/types.js CHANGED
@@ -95,7 +95,7 @@ exports.OracleSource = OracleSource;
95
95
  OracleSource.PYTH = { pyth: {} };
96
96
  OracleSource.PYTH_1K = { pyth1K: {} };
97
97
  OracleSource.PYTH_1M = { pyth1M: {} };
98
- // static readonly SWITCHBOARD = { switchboard: {} };
98
+ OracleSource.SWITCHBOARD = { switchboard: {} };
99
99
  OracleSource.QUOTE_ASSET = { quoteAsset: {} };
100
100
  OracleSource.PYTH_STABLE_COIN = { pythStableCoin: {} };
101
101
  class OrderType {
package/lib/user.js CHANGED
@@ -717,7 +717,7 @@ class User {
717
717
  marginRatio = numericConstants_1.ZERO;
718
718
  }
719
719
  const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
720
- const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
720
+ const quoteOraclePriceData = this.driftClient.getOracleDataForSpotMarket(numericConstants_1.QUOTE_SPOT_MARKET_INDEX);
721
721
  let quotePrice;
722
722
  if (strict) {
723
723
  quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
@@ -1198,7 +1198,7 @@ class User {
1198
1198
  calculateFreeCollateralDeltaForSpot(market, signedTokenAmount, marginCategory = 'Maintenance') {
1199
1199
  const tokenPrecision = new _1.BN(Math.pow(10, market.decimals));
1200
1200
  if (signedTokenAmount.gt(numericConstants_1.ZERO)) {
1201
- const assetWeight = (0, spotBalance_1.calculateAssetWeight)(signedTokenAmount, this.driftClient.getOraclePriceDataAndSlot(market.oracle).data.price, market, marginCategory);
1201
+ const assetWeight = (0, spotBalance_1.calculateAssetWeight)(signedTokenAmount, this.driftClient.getOracleDataForSpotMarket(market.marketIndex).price, market, marginCategory);
1202
1202
  return numericConstants_1.QUOTE_PRECISION.mul(assetWeight)
1203
1203
  .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION)
1204
1204
  .mul(signedTokenAmount)
@@ -1320,7 +1320,7 @@ class User {
1320
1320
  */
1321
1321
  getMaxTradeSizeUSDCForSpot(targetMarketIndex, direction, currentQuoteAssetValue, currentSpotMarketNetValue) {
1322
1322
  const market = this.driftClient.getSpotMarketAccount(targetMarketIndex);
1323
- const oraclePrice = this.driftClient.getOraclePriceDataAndSlot(market.oracle).data.price;
1323
+ const oraclePrice = this.driftClient.getOracleDataForSpotMarket(targetMarketIndex).price;
1324
1324
  currentQuoteAssetValue = this.getSpotMarketAssetValue(numericConstants_1.QUOTE_SPOT_MARKET_INDEX);
1325
1325
  currentSpotMarketNetValue =
1326
1326
  currentSpotMarketNetValue !== null && currentSpotMarketNetValue !== void 0 ? currentSpotMarketNetValue : this.getSpotPositionValue(targetMarketIndex);
@@ -1872,12 +1872,12 @@ class User {
1872
1872
  for (const perpPosition of this.getActivePerpPositions()) {
1873
1873
  const settledLpPosition = this.getPerpPositionWithLPSettle(perpPosition.marketIndex, perpPosition)[0];
1874
1874
  const perpMarket = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
1875
- const oraclePriceData = this.driftClient.getOraclePriceDataAndSlot(perpMarket.amm.oracle).data;
1875
+ const oraclePriceData = this.driftClient.getOracleDataForPerpMarket(perpMarket.marketIndex);
1876
1876
  const oraclePrice = oraclePriceData.price;
1877
1877
  const worstCaseBaseAmount = (0, margin_1.calculateWorstCaseBaseAssetAmount)(settledLpPosition);
1878
1878
  const marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(perpMarket, worstCaseBaseAmount.abs(), marginCategory, this.getUserAccount().maxMarginRatio));
1879
1879
  const quoteSpotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
1880
- const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
1880
+ const quoteOraclePriceData = this.driftClient.getOracleDataForSpotMarket(numericConstants_1.QUOTE_SPOT_MARKET_INDEX);
1881
1881
  const baseAssetValue = worstCaseBaseAmount
1882
1882
  .abs()
1883
1883
  .mul(oraclePrice)
@@ -1999,14 +1999,10 @@ class User {
1999
1999
  return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders).sub(currentPerpPositionValueUSDC);
2000
2000
  }
2001
2001
  getOracleDataForPerpMarket(marketIndex) {
2002
- const oracleKey = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
2003
- const oracleData = this.driftClient.getOraclePriceDataAndSlot(oracleKey).data;
2004
- return oracleData;
2002
+ return this.driftClient.getOracleDataForPerpMarket(marketIndex);
2005
2003
  }
2006
2004
  getOracleDataForSpotMarket(marketIndex) {
2007
- const oracleKey = this.driftClient.getSpotMarketAccount(marketIndex).oracle;
2008
- const oracleData = this.driftClient.getOraclePriceDataAndSlot(oracleKey).data;
2009
- return oracleData;
2005
+ return this.driftClient.getOracleDataForSpotMarket(marketIndex);
2010
2006
  }
2011
2007
  }
2012
2008
  exports.User = User;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.67.0-beta.0",
3
+ "version": "2.67.0-beta.2",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -49,7 +49,9 @@ export class PollingDriftClientAccountSubscriber
49
49
 
50
50
  state?: DataAndSlot<StateAccount>;
51
51
  perpMarket = new Map<number, DataAndSlot<PerpMarketAccount>>();
52
+ perpOracleMap = new Map<number, PublicKey>();
52
53
  spotMarket = new Map<number, DataAndSlot<SpotMarketAccount>>();
54
+ spotOracleMap = new Map<number, PublicKey>();
53
55
  oracles = new Map<string, DataAndSlot<OraclePriceData>>();
54
56
  user?: DataAndSlot<UserAccount>;
55
57
 
@@ -114,6 +116,9 @@ export class PollingDriftClientAccountSubscriber
114
116
  this.eventEmitter.emit('update');
115
117
  }
116
118
 
119
+ this.setPerpOracleMap();
120
+ this.setSpotOracleMap();
121
+
117
122
  this.isSubscribing = false;
118
123
  this.isSubscribed = subscriptionSucceeded;
119
124
  this.subscriptionPromiseResolver(subscriptionSucceeded);
@@ -367,6 +372,7 @@ export class PollingDriftClientAccountSubscriber
367
372
  const accountToPoll = this.accountsToPoll.get(marketPublicKey.toString());
368
373
 
369
374
  await this.addAccountToAccountLoader(accountToPoll);
375
+ this.setSpotOracleMap();
370
376
  return true;
371
377
  }
372
378
 
@@ -383,6 +389,7 @@ export class PollingDriftClientAccountSubscriber
383
389
  await this.addPerpMarketAccountToPoll(marketIndex);
384
390
  const accountToPoll = this.accountsToPoll.get(marketPublicKey.toString());
385
391
  await this.addAccountToAccountLoader(accountToPoll);
392
+ this.setPerpOracleMap();
386
393
  return true;
387
394
  }
388
395
 
@@ -402,6 +409,26 @@ export class PollingDriftClientAccountSubscriber
402
409
  return true;
403
410
  }
404
411
 
412
+ private setPerpOracleMap() {
413
+ const perpMarkets = this.getMarketAccountsAndSlots();
414
+ for (const perpMarket of perpMarkets) {
415
+ const perpMarketAccount = perpMarket.data;
416
+ const perpMarketIndex = perpMarketAccount.marketIndex;
417
+ const oracle = perpMarketAccount.amm.oracle;
418
+ this.perpOracleMap.set(perpMarketIndex, oracle);
419
+ }
420
+ }
421
+
422
+ private setSpotOracleMap() {
423
+ const spotMarkets = this.getSpotMarketAccountsAndSlots();
424
+ for (const spotMarket of spotMarkets) {
425
+ const spotMarketAccount = spotMarket.data;
426
+ const spotMarketIndex = spotMarketAccount.marketIndex;
427
+ const oracle = spotMarketAccount.oracle;
428
+ this.spotOracleMap.set(spotMarketIndex, oracle);
429
+ }
430
+ }
431
+
405
432
  assertIsSubscribed(): void {
406
433
  if (!this.isSubscribed) {
407
434
  throw new NotSubscribedError(
@@ -449,6 +476,49 @@ export class PollingDriftClientAccountSubscriber
449
476
  return this.oracles.get(oraclePublicKey.toString());
450
477
  }
451
478
 
479
+ public getOraclePriceDataAndSlotForPerpMarket(
480
+ marketIndex: number
481
+ ): DataAndSlot<OraclePriceData> | undefined {
482
+ const perpMarketAccount = this.getMarketAccountAndSlot(marketIndex);
483
+ const oracle = this.perpOracleMap.get(marketIndex);
484
+ if (!perpMarketAccount || !oracle) {
485
+ return undefined;
486
+ }
487
+
488
+ if (!perpMarketAccount.data.amm.oracle.equals(oracle)) {
489
+ // If the oracle has changed, we need to update the oracle map in background
490
+ this.addOracle({
491
+ source: perpMarketAccount.data.amm.oracleSource,
492
+ publicKey: perpMarketAccount.data.amm.oracle,
493
+ }).then(() => {
494
+ this.setPerpOracleMap();
495
+ });
496
+ }
497
+
498
+ return this.getOraclePriceDataAndSlot(oracle);
499
+ }
500
+
501
+ public getOraclePriceDataAndSlotForSpotMarket(
502
+ marketIndex: number
503
+ ): DataAndSlot<OraclePriceData> | undefined {
504
+ const spotMarketAccount = this.getSpotMarketAccountAndSlot(marketIndex);
505
+ const oracle = this.spotOracleMap.get(marketIndex);
506
+ if (!spotMarketAccount || !oracle) {
507
+ return undefined;
508
+ }
509
+ if (!spotMarketAccount.data.oracle.equals(oracle)) {
510
+ // If the oracle has changed, we need to update the oracle map in background
511
+ this.addOracle({
512
+ source: spotMarketAccount.data.oracleSource,
513
+ publicKey: spotMarketAccount.data.oracle,
514
+ }).then(() => {
515
+ this.setSpotOracleMap();
516
+ });
517
+ }
518
+
519
+ return this.getOraclePriceDataAndSlot(oracle);
520
+ }
521
+
452
522
  public updateAccountLoaderPollingFrequency(pollingFrequency: number): void {
453
523
  this.accountLoader.updatePollingFrequency(pollingFrequency);
454
524
  }
@@ -71,6 +71,12 @@ export interface DriftClientAccountSubscriber {
71
71
  getOraclePriceDataAndSlot(
72
72
  oraclePublicKey: PublicKey
73
73
  ): DataAndSlot<OraclePriceData> | undefined;
74
+ getOraclePriceDataAndSlotForPerpMarket(
75
+ marketIndex: number
76
+ ): DataAndSlot<OraclePriceData> | undefined;
77
+ getOraclePriceDataAndSlotForSpotMarket(
78
+ marketIndex: number
79
+ ): DataAndSlot<OraclePriceData> | undefined;
74
80
 
75
81
  updateAccountLoaderPollingFrequency?: (pollingFrequency: number) => void;
76
82
  }
@@ -41,10 +41,12 @@ export class WebSocketDriftClientAccountSubscriber
41
41
  number,
42
42
  AccountSubscriber<PerpMarketAccount>
43
43
  >();
44
+ perpOracleMap = new Map<number, PublicKey>();
44
45
  spotMarketAccountSubscribers = new Map<
45
46
  number,
46
47
  AccountSubscriber<SpotMarketAccount>
47
48
  >();
49
+ spotOracleMap = new Map<number, PublicKey>();
48
50
  oracleSubscribers = new Map<string, AccountSubscriber<OraclePriceData>>();
49
51
 
50
52
  private isSubscribing = false;
@@ -123,6 +125,9 @@ export class WebSocketDriftClientAccountSubscriber
123
125
 
124
126
  this.eventEmitter.emit('update');
125
127
 
128
+ this.setPerpOracleMap();
129
+ this.setSpotOracleMap();
130
+
126
131
  this.isSubscribing = false;
127
132
  this.isSubscribed = true;
128
133
  this.subscriptionPromiseResolver(true);
@@ -280,14 +285,18 @@ export class WebSocketDriftClientAccountSubscriber
280
285
  if (this.spotMarketAccountSubscribers.has(marketIndex)) {
281
286
  return true;
282
287
  }
283
- return this.subscribeToSpotMarketAccount(marketIndex);
288
+ const subscriptionSuccess = this.subscribeToSpotMarketAccount(marketIndex);
289
+ this.setSpotOracleMap();
290
+ return subscriptionSuccess;
284
291
  }
285
292
 
286
293
  async addPerpMarket(marketIndex: number): Promise<boolean> {
287
294
  if (this.perpMarketAccountSubscribers.has(marketIndex)) {
288
295
  return true;
289
296
  }
290
- return this.subscribeToPerpMarketAccount(marketIndex);
297
+ const subscriptionSuccess = this.subscribeToPerpMarketAccount(marketIndex);
298
+ this.setPerpOracleMap();
299
+ return subscriptionSuccess;
291
300
  }
292
301
 
293
302
  async addOracle(oracleInfo: OracleInfo): Promise<boolean> {
@@ -302,6 +311,32 @@ export class WebSocketDriftClientAccountSubscriber
302
311
  return this.subscribeToOracle(oracleInfo);
303
312
  }
304
313
 
314
+ private setPerpOracleMap() {
315
+ const perpMarkets = this.getMarketAccountsAndSlots();
316
+ for (const perpMarket of perpMarkets) {
317
+ if (!perpMarket) {
318
+ continue;
319
+ }
320
+ const perpMarketAccount = perpMarket.data;
321
+ const perpMarketIndex = perpMarketAccount.marketIndex;
322
+ const oracle = perpMarketAccount.amm.oracle;
323
+ this.perpOracleMap.set(perpMarketIndex, oracle);
324
+ }
325
+ }
326
+
327
+ private setSpotOracleMap() {
328
+ const spotMarkets = this.getSpotMarketAccountsAndSlots();
329
+ for (const spotMarket of spotMarkets) {
330
+ if (!spotMarket) {
331
+ continue;
332
+ }
333
+ const spotMarketAccount = spotMarket.data;
334
+ const spotMarketIndex = spotMarketAccount.marketIndex;
335
+ const oracle = spotMarketAccount.oracle;
336
+ this.spotOracleMap.set(spotMarketIndex, oracle);
337
+ }
338
+ }
339
+
305
340
  assertIsSubscribed(): void {
306
341
  if (!this.isSubscribed) {
307
342
  throw new NotSubscribedError(
@@ -353,4 +388,48 @@ export class WebSocketDriftClientAccountSubscriber
353
388
  }
354
389
  return this.oracleSubscribers.get(oraclePublicKey.toString()).dataAndSlot;
355
390
  }
391
+
392
+ public getOraclePriceDataAndSlotForPerpMarket(
393
+ marketIndex: number
394
+ ): DataAndSlot<OraclePriceData> | undefined {
395
+ const perpMarketAccount = this.getMarketAccountAndSlot(marketIndex);
396
+ const oracle = this.perpOracleMap.get(marketIndex);
397
+ if (!perpMarketAccount || !oracle) {
398
+ return undefined;
399
+ }
400
+
401
+ if (!perpMarketAccount.data.amm.oracle.equals(oracle)) {
402
+ // If the oracle has changed, we need to update the oracle map in background
403
+ this.addOracle({
404
+ source: perpMarketAccount.data.amm.oracleSource,
405
+ publicKey: perpMarketAccount.data.amm.oracle,
406
+ }).then(() => {
407
+ this.setPerpOracleMap();
408
+ });
409
+ }
410
+
411
+ return this.getOraclePriceDataAndSlot(oracle);
412
+ }
413
+
414
+ public getOraclePriceDataAndSlotForSpotMarket(
415
+ marketIndex: number
416
+ ): DataAndSlot<OraclePriceData> | undefined {
417
+ const spotMarketAccount = this.getSpotMarketAccountAndSlot(marketIndex);
418
+ const oracle = this.spotOracleMap.get(marketIndex);
419
+ if (!spotMarketAccount || !oracle) {
420
+ return undefined;
421
+ }
422
+
423
+ if (!spotMarketAccount.data.oracle.equals(oracle)) {
424
+ // If the oracle has changed, we need to update the oracle map in background
425
+ this.addOracle({
426
+ source: spotMarketAccount.data.oracleSource,
427
+ publicKey: spotMarketAccount.data.oracle,
428
+ }).then(() => {
429
+ this.setSpotOracleMap();
430
+ });
431
+ }
432
+
433
+ return this.getOraclePriceDataAndSlot(oracle);
434
+ }
356
435
  }
@@ -5873,17 +5873,15 @@ export class DriftClient {
5873
5873
  }
5874
5874
 
5875
5875
  public getOracleDataForPerpMarket(marketIndex: number): OraclePriceData {
5876
- const oracleKey = this.getPerpMarketAccount(marketIndex).amm.oracle;
5877
- const oracleData = this.getOraclePriceDataAndSlot(oracleKey).data;
5878
-
5879
- return oracleData;
5876
+ return this.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket(
5877
+ marketIndex
5878
+ ).data;
5880
5879
  }
5881
5880
 
5882
5881
  public getOracleDataForSpotMarket(marketIndex: number): OraclePriceData {
5883
- const oracleKey = this.getSpotMarketAccount(marketIndex).oracle;
5884
- const oracleData = this.getOraclePriceDataAndSlot(oracleKey).data;
5885
-
5886
- return oracleData;
5882
+ return this.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket(
5883
+ marketIndex
5884
+ ).data;
5887
5885
  }
5888
5886
 
5889
5887
  public async initializeInsuranceFundStake(
@@ -6185,9 +6183,13 @@ export class DriftClient {
6185
6183
  }
6186
6184
  }
6187
6185
 
6186
+ const userAccountExists =
6187
+ !!this.getUser()?.accountSubscriber?.isSubscribed &&
6188
+ (await this.checkIfAccountExists(this.getUser().userAccountPublicKey));
6189
+
6188
6190
  const remainingAccounts = this.getRemainingAccounts({
6189
- userAccounts: [this.getUserAccount()],
6190
- useMarketLastSlotCache: true,
6191
+ userAccounts: userAccountExists ? [this.getUserAccount()] : [],
6192
+ useMarketLastSlotCache: false,
6191
6193
  writableSpotMarketIndexes: [marketIndex],
6192
6194
  });
6193
6195
 
@@ -5,6 +5,7 @@ import { PythClient } from '../oracles/pythClient';
5
5
  // import { SwitchboardClient } from '../oracles/switchboardClient';
6
6
  import { QuoteAssetOracleClient } from '../oracles/quoteAssetOracleClient';
7
7
  import { BN } from '@coral-xyz/anchor';
8
+ import { SwitchboardClient } from '../oracles/switchboardClient';
8
9
 
9
10
  export function getOracleClient(
10
11
  oracleSource: OracleSource,
@@ -26,9 +27,9 @@ export function getOracleClient(
26
27
  return new PythClient(connection, undefined, true);
27
28
  }
28
29
 
29
- // if (isVariant(oracleSource, 'switchboard')) {
30
- // return new SwitchboardClient(connection);
31
- // }
30
+ if (isVariant(oracleSource, 'switchboard')) {
31
+ return new SwitchboardClient(connection);
32
+ }
32
33
 
33
34
  if (isVariant(oracleSource, 'quoteAsset')) {
34
35
  return new QuoteAssetOracleClient();