@drift-labs/sdk 2.96.0-beta.9 → 2.97.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.
Files changed (69) hide show
  1. package/README.md +3 -0
  2. package/VERSION +1 -1
  3. package/bun.lockb +0 -0
  4. package/lib/accounts/pollingDriftClientAccountSubscriber.d.ts +5 -3
  5. package/lib/accounts/pollingDriftClientAccountSubscriber.js +24 -1
  6. package/lib/accounts/types.d.ts +5 -0
  7. package/lib/accounts/types.js +7 -1
  8. package/lib/accounts/utils.d.ts +7 -0
  9. package/lib/accounts/utils.js +33 -1
  10. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +5 -4
  11. package/lib/accounts/webSocketDriftClientAccountSubscriber.js +24 -1
  12. package/lib/config.d.ts +6 -1
  13. package/lib/config.js +10 -1
  14. package/lib/constants/perpMarkets.js +33 -1
  15. package/lib/constants/spotMarkets.js +10 -0
  16. package/lib/constants/txConstants.d.ts +1 -0
  17. package/lib/constants/txConstants.js +4 -0
  18. package/lib/driftClient.d.ts +36 -8
  19. package/lib/driftClient.js +166 -43
  20. package/lib/driftClientConfig.d.ts +3 -0
  21. package/lib/events/types.js +1 -5
  22. package/lib/idl/drift.json +169 -1
  23. package/lib/index.d.ts +1 -0
  24. package/lib/index.js +1 -0
  25. package/lib/orderParams.js +8 -8
  26. package/lib/orderSubscriber/OrderSubscriber.js +1 -6
  27. package/lib/tokenFaucet.js +2 -1
  28. package/lib/tx/baseTxSender.d.ts +0 -1
  29. package/lib/tx/baseTxSender.js +8 -26
  30. package/lib/tx/fastSingleTxSender.js +2 -2
  31. package/lib/tx/forwardOnlyTxSender.js +2 -2
  32. package/lib/tx/reportTransactionError.d.ts +20 -0
  33. package/lib/tx/reportTransactionError.js +103 -0
  34. package/lib/tx/retryTxSender.js +2 -2
  35. package/lib/tx/txHandler.js +10 -7
  36. package/lib/tx/whileValidTxSender.d.ts +4 -5
  37. package/lib/tx/whileValidTxSender.js +16 -17
  38. package/lib/types.d.ts +22 -1
  39. package/lib/types.js +6 -1
  40. package/lib/util/TransactionConfirmationManager.d.ts +16 -0
  41. package/lib/util/TransactionConfirmationManager.js +174 -0
  42. package/package.json +4 -3
  43. package/src/accounts/pollingDriftClientAccountSubscriber.ts +41 -5
  44. package/src/accounts/types.ts +6 -0
  45. package/src/accounts/utils.ts +42 -0
  46. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +40 -5
  47. package/src/config.ts +17 -1
  48. package/src/constants/perpMarkets.ts +35 -1
  49. package/src/constants/spotMarkets.ts +11 -0
  50. package/src/constants/txConstants.ts +1 -0
  51. package/src/driftClient.ts +346 -53
  52. package/src/driftClientConfig.ts +3 -0
  53. package/src/events/types.ts +1 -5
  54. package/src/idl/drift.json +169 -1
  55. package/src/index.ts +1 -0
  56. package/src/orderParams.ts +20 -12
  57. package/src/orderSubscriber/OrderSubscriber.ts +2 -5
  58. package/src/tokenFaucet.ts +2 -2
  59. package/src/tx/baseTxSender.ts +10 -32
  60. package/src/tx/fastSingleTxSender.ts +2 -2
  61. package/src/tx/forwardOnlyTxSender.ts +2 -2
  62. package/src/tx/reportTransactionError.ts +159 -0
  63. package/src/tx/retryTxSender.ts +2 -2
  64. package/src/tx/txHandler.ts +8 -2
  65. package/src/tx/whileValidTxSender.ts +18 -27
  66. package/src/types.ts +31 -1
  67. package/src/util/TransactionConfirmationManager.ts +292 -0
  68. package/tests/ci/verifyConstants.ts +13 -0
  69. package/tests/tx/TransactionConfirmationManager.test.ts +305 -0
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransactionConfirmationManager = void 0;
4
+ const config_1 = require("../config");
5
+ const __1 = require("..");
6
+ const txConstants_1 = require("../constants/txConstants");
7
+ const reportTransactionError_1 = require("../tx/reportTransactionError");
8
+ const promiseTimeout_1 = require("./promiseTimeout");
9
+ const confirmationStatusValues = {
10
+ processed: 0,
11
+ confirmed: 1,
12
+ finalized: 2,
13
+ };
14
+ /**
15
+ * Class to await for transaction confirmations in an optimised manner. It tracks a shared list of all pending transactions and fetches them in bulk in a shared RPC request whenever they have an "overlapping" polling interval. E.g. tx1 with an interval of 200ms and tx2 with an interval of 300ms (if sent at the same time) will be fetched together at at 600ms, 1200ms, 1800ms, etc.
16
+ */
17
+ class TransactionConfirmationManager {
18
+ constructor(connection) {
19
+ this.pendingConfirmations = new Map();
20
+ this.intervalId = null;
21
+ this.connection = connection;
22
+ }
23
+ async confirmTransactionWebSocket(txSig, timeout = 30000, desiredConfirmationStatus = config_1.DEFAULT_CONFIRMATION_OPTS.commitment) {
24
+ const start = Date.now();
25
+ const subscriptionCommitment = desiredConfirmationStatus || config_1.DEFAULT_CONFIRMATION_OPTS.commitment;
26
+ let response = null;
27
+ let subscriptionId;
28
+ const confirmationPromise = new Promise((resolve, reject) => {
29
+ try {
30
+ subscriptionId = this.connection.onSignature(txSig, (result, context) => {
31
+ response = {
32
+ context,
33
+ value: result,
34
+ };
35
+ resolve(null);
36
+ }, subscriptionCommitment);
37
+ }
38
+ catch (err) {
39
+ reject(err);
40
+ }
41
+ });
42
+ // We do a one-shot confirmation check just in case the transaction is ALREADY confirmed when we create the websocket confirmation .. We want to run this concurrently with the onSignature subscription. If this returns true then we can return early as the transaction has already been confirmed.
43
+ const oneShotConfirmationPromise = this.connection.getSignatureStatuses([
44
+ txSig,
45
+ ]);
46
+ const resolveReference = {};
47
+ // This is the promise we are waiting on to resolve the overall confirmation. It will resolve the faster of a positive oneShot confirmation, or the websocket confirmation, or the timeout.
48
+ const overallWaitingForConfirmationPromise = new Promise((resolve) => {
49
+ resolveReference.resolve = resolve;
50
+ });
51
+ // Await for the one shot confirmation and resolve the waiting promise if we get a positive confirmation result
52
+ oneShotConfirmationPromise.then(async (oneShotResponse) => {
53
+ var _a, _b;
54
+ if (!oneShotResponse || !((_a = oneShotResponse === null || oneShotResponse === void 0 ? void 0 : oneShotResponse.value) === null || _a === void 0 ? void 0 : _a[0]))
55
+ return;
56
+ const resultValue = oneShotResponse.value[0];
57
+ if (resultValue.err) {
58
+ await (0, reportTransactionError_1.throwTransactionError)(txSig, this.connection);
59
+ }
60
+ if (this.checkStatusMatchesDesiredConfirmationStatus(resultValue, desiredConfirmationStatus)) {
61
+ response = {
62
+ context: oneShotResponse.context,
63
+ value: oneShotResponse.value[0],
64
+ };
65
+ (_b = resolveReference.resolve) === null || _b === void 0 ? void 0 : _b.call(resolveReference);
66
+ }
67
+ }, (onRejected) => {
68
+ throw onRejected;
69
+ });
70
+ // Await for the websocket confirmation with the configured timeout
71
+ (0, promiseTimeout_1.promiseTimeout)(confirmationPromise, timeout).then(() => {
72
+ var _a;
73
+ (_a = resolveReference.resolve) === null || _a === void 0 ? void 0 : _a.call(resolveReference);
74
+ }, (onRejected) => {
75
+ throw onRejected;
76
+ });
77
+ try {
78
+ await overallWaitingForConfirmationPromise;
79
+ }
80
+ finally {
81
+ if (subscriptionId !== undefined) {
82
+ this.connection.removeSignatureListener(subscriptionId);
83
+ }
84
+ }
85
+ const duration = (Date.now() - start) / 1000;
86
+ if (response === null) {
87
+ throw new __1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${txSig} using the Solana Explorer or CLI tools.`, txConstants_1.NOT_CONFIRMED_ERROR_CODE);
88
+ }
89
+ return response;
90
+ }
91
+ async confirmTransactionPolling(txSig, desiredConfirmationStatus = config_1.DEFAULT_CONFIRMATION_OPTS.commitment, timeout = 30000, pollInterval = 1000, searchTransactionHistory = false) {
92
+ // Interval must be > 400ms and a multiple of 100ms
93
+ if (pollInterval < 400 || pollInterval % 100 !== 0) {
94
+ throw new Error('Transaction confirmation polling interval must be at least 400ms and a multiple of 100ms');
95
+ }
96
+ return new Promise((resolve, reject) => {
97
+ this.pendingConfirmations.set(txSig, {
98
+ txSig,
99
+ desiredConfirmationStatus,
100
+ timeout,
101
+ pollInterval,
102
+ searchTransactionHistory,
103
+ startTime: Date.now(),
104
+ resolve,
105
+ reject,
106
+ });
107
+ if (!this.intervalId) {
108
+ this.startConfirmationLoop();
109
+ }
110
+ });
111
+ }
112
+ startConfirmationLoop() {
113
+ this.intervalId = setInterval(() => this.checkPendingConfirmations(), 100);
114
+ }
115
+ async checkPendingConfirmations() {
116
+ const now = Date.now();
117
+ const transactionsToCheck = [];
118
+ for (const [txSig, request] of this.pendingConfirmations.entries()) {
119
+ if (now - request.startTime >= request.timeout) {
120
+ request.reject(new Error(`Transaction confirmation timeout after ${request.timeout}ms`));
121
+ this.pendingConfirmations.delete(txSig);
122
+ }
123
+ else if ((now - request.startTime) % request.pollInterval < 100) {
124
+ transactionsToCheck.push(request);
125
+ }
126
+ }
127
+ if (transactionsToCheck.length > 0) {
128
+ await this.checkTransactionStatuses(transactionsToCheck);
129
+ }
130
+ if (this.pendingConfirmations.size === 0 && this.intervalId) {
131
+ clearInterval(this.intervalId);
132
+ this.intervalId = null;
133
+ }
134
+ }
135
+ checkStatusMatchesDesiredConfirmationStatus(status, desiredConfirmationStatus) {
136
+ if (status.confirmationStatus &&
137
+ confirmationStatusValues[status.confirmationStatus] >=
138
+ confirmationStatusValues[desiredConfirmationStatus]) {
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+ async checkTransactionStatuses(requests) {
144
+ const txSigs = requests.map((request) => request.txSig);
145
+ const { value: statuses } = await this.connection.getSignatureStatuses(txSigs, {
146
+ searchTransactionHistory: requests.some((req) => req.searchTransactionHistory),
147
+ });
148
+ if (!statuses || statuses.length !== txSigs.length) {
149
+ throw new Error('Failed to get signature statuses');
150
+ }
151
+ for (let i = 0; i < statuses.length; i++) {
152
+ const status = statuses[i];
153
+ const request = requests[i];
154
+ if (status === null) {
155
+ continue;
156
+ }
157
+ if (status.err) {
158
+ this.pendingConfirmations.delete(request.txSig);
159
+ request.reject(await (0, reportTransactionError_1.getTransactionErrorFromTxSig)(request.txSig, this.connection));
160
+ continue;
161
+ }
162
+ if (confirmationStatusValues[status.confirmationStatus] === undefined ||
163
+ confirmationStatusValues[request.desiredConfirmationStatus] ===
164
+ undefined) {
165
+ throw new Error(`Invalid confirmation status when awaiting confirmation: ${status.confirmationStatus}`);
166
+ }
167
+ if (this.checkStatusMatchesDesiredConfirmationStatus(status, request.desiredConfirmationStatus)) {
168
+ request.resolve(status);
169
+ this.pendingConfirmations.delete(request.txSig);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ exports.TransactionConfirmationManager = TransactionConfirmationManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.96.0-beta.9",
3
+ "version": "2.97.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -37,14 +37,14 @@
37
37
  "@coral-xyz/anchor": "0.28.0",
38
38
  "@coral-xyz/anchor-30": "npm:@coral-xyz/anchor@0.30.1",
39
39
  "@ellipsis-labs/phoenix-sdk": "^1.4.2",
40
- "@openbook-dex/openbook-v2": "^0.2.10",
40
+ "@openbook-dex/openbook-v2": "0.2.10",
41
41
  "@project-serum/serum": "^0.13.38",
42
42
  "@pythnetwork/client": "2.5.3",
43
43
  "@pythnetwork/price-service-sdk": "^1.7.1",
44
44
  "@pythnetwork/pyth-solana-receiver": "^0.7.0",
45
45
  "@solana/spl-token": "0.3.7",
46
46
  "@solana/web3.js": "1.92.3",
47
- "@switchboard-xyz/on-demand": "1.2.32",
47
+ "@switchboard-xyz/on-demand": "1.2.42",
48
48
  "anchor-bankrun": "^0.3.0",
49
49
  "node-cache": "^5.1.2",
50
50
  "rpc-websockets": "7.5.1",
@@ -56,6 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@types/big.js": "^6.2.0",
58
58
  "@types/bn.js": "^5.1.3",
59
+ "@types/bs58": "^4.0.4",
59
60
  "@types/chai": "^4.3.1",
60
61
  "@types/jest": "^28.1.3",
61
62
  "@types/mocha": "^9.1.1",
@@ -1,6 +1,7 @@
1
1
  import {
2
- DataAndSlot,
3
2
  AccountToPoll,
3
+ DataAndSlot,
4
+ DelistedMarketSetting,
4
5
  DriftClientAccountEvents,
5
6
  DriftClientAccountSubscriber,
6
7
  NotSubscribedError,
@@ -10,18 +11,18 @@ import { Program } from '@coral-xyz/anchor';
10
11
  import StrictEventEmitter from 'strict-event-emitter-types';
11
12
  import { EventEmitter } from 'events';
12
13
  import {
13
- SpotMarketAccount,
14
14
  PerpMarketAccount,
15
+ SpotMarketAccount,
15
16
  StateAccount,
16
17
  UserAccount,
17
18
  } from '../types';
18
19
  import {
19
20
  getDriftStateAccountPublicKey,
20
- getSpotMarketPublicKey,
21
21
  getPerpMarketPublicKey,
22
+ getSpotMarketPublicKey,
22
23
  } from '../addresses/pda';
23
24
  import { BulkAccountLoader } from './bulkAccountLoader';
24
- import { capitalize } from './utils';
25
+ import { capitalize, findDelistedPerpMarketsAndOracles } from './utils';
25
26
  import { PublicKey } from '@solana/web3.js';
26
27
  import { OracleInfo, OraclePriceData } from '../oracles/types';
27
28
  import { OracleClientCache } from '../oracles/oracleClientCache';
@@ -58,6 +59,7 @@ export class PollingDriftClientAccountSubscriber
58
59
  spotOracleStringMap = new Map<number, string>();
59
60
  oracles = new Map<string, DataAndSlot<OraclePriceData>>();
60
61
  user?: DataAndSlot<UserAccount>;
62
+ delistedMarketSetting: DelistedMarketSetting;
61
63
 
62
64
  private isSubscribing = false;
63
65
  private subscriptionPromise: Promise<boolean>;
@@ -69,7 +71,8 @@ export class PollingDriftClientAccountSubscriber
69
71
  perpMarketIndexes: number[],
70
72
  spotMarketIndexes: number[],
71
73
  oracleInfos: OracleInfo[],
72
- shouldFindAllMarketsAndOracles: boolean
74
+ shouldFindAllMarketsAndOracles: boolean,
75
+ delistedMarketSetting: DelistedMarketSetting
73
76
  ) {
74
77
  this.isSubscribed = false;
75
78
  this.program = program;
@@ -79,6 +82,7 @@ export class PollingDriftClientAccountSubscriber
79
82
  this.spotMarketIndexes = spotMarketIndexes;
80
83
  this.oracleInfos = oracleInfos;
81
84
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
85
+ this.delistedMarketSetting = delistedMarketSetting;
82
86
  }
83
87
 
84
88
  public async subscribe(): Promise<boolean> {
@@ -120,6 +124,8 @@ export class PollingDriftClientAccountSubscriber
120
124
  this.eventEmitter.emit('update');
121
125
  }
122
126
 
127
+ this.handleDelistedMarkets();
128
+
123
129
  await Promise.all([this.setPerpOracleMap(), this.setSpotOracleMap()]);
124
130
 
125
131
  this.isSubscribing = false;
@@ -500,6 +506,36 @@ export class PollingDriftClientAccountSubscriber
500
506
  await Promise.all(oraclePromises);
501
507
  }
502
508
 
509
+ handleDelistedMarkets(): void {
510
+ if (this.delistedMarketSetting === DelistedMarketSetting.Subscribe) {
511
+ return;
512
+ }
513
+
514
+ const { perpMarketIndexes, oracles } = findDelistedPerpMarketsAndOracles(
515
+ this.getMarketAccountsAndSlots(),
516
+ this.getSpotMarketAccountsAndSlots()
517
+ );
518
+
519
+ for (const perpMarketIndex of perpMarketIndexes) {
520
+ const perpMarketPubkey = this.perpMarket.get(perpMarketIndex).data.pubkey;
521
+ const callbackId = this.accountsToPoll.get(
522
+ perpMarketPubkey.toBase58()
523
+ ).callbackId;
524
+ this.accountLoader.removeAccount(perpMarketPubkey, callbackId);
525
+ if (this.delistedMarketSetting === DelistedMarketSetting.Discard) {
526
+ this.perpMarket.delete(perpMarketIndex);
527
+ }
528
+ }
529
+
530
+ for (const oracle of oracles) {
531
+ const callbackId = this.oraclesToPoll.get(oracle.toBase58()).callbackId;
532
+ this.accountLoader.removeAccount(oracle, callbackId);
533
+ if (this.delistedMarketSetting === DelistedMarketSetting.Discard) {
534
+ this.oracles.delete(oracle.toBase58());
535
+ }
536
+ }
537
+ }
538
+
503
539
  assertIsSubscribed(): void {
504
540
  if (!this.isSubscribed) {
505
541
  throw new NotSubscribedError(
@@ -84,6 +84,12 @@ export interface DriftClientAccountSubscriber {
84
84
  updateAccountLoaderPollingFrequency?: (pollingFrequency: number) => void;
85
85
  }
86
86
 
87
+ export enum DelistedMarketSetting {
88
+ Unsubscribe,
89
+ Subscribe,
90
+ Discard,
91
+ }
92
+
87
93
  export interface UserAccountEvents {
88
94
  userAccountUpdate: (payload: UserAccount) => void;
89
95
  update: void;
@@ -1,3 +1,45 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { DataAndSlot } from './types';
3
+ import { isVariant, PerpMarketAccount, SpotMarketAccount } from '../types';
4
+
1
5
  export function capitalize(value: string): string {
2
6
  return value[0].toUpperCase() + value.slice(1);
3
7
  }
8
+
9
+ export function findDelistedPerpMarketsAndOracles(
10
+ perpMarkets: DataAndSlot<PerpMarketAccount>[],
11
+ spotMarkets: DataAndSlot<SpotMarketAccount>[]
12
+ ): { perpMarketIndexes: number[]; oracles: PublicKey[] } {
13
+ const delistedPerpMarketIndexes = [];
14
+ const delistedOracles = [];
15
+ for (const perpMarket of perpMarkets) {
16
+ if (!perpMarket.data) {
17
+ continue;
18
+ }
19
+
20
+ if (isVariant(perpMarket.data.status, 'delisted')) {
21
+ delistedPerpMarketIndexes.push(perpMarket.data.marketIndex);
22
+ delistedOracles.push(perpMarket.data.amm.oracle);
23
+ }
24
+ }
25
+
26
+ // make sure oracle isn't used by spot market
27
+ const filteredDelistedOracles = [];
28
+ for (const delistedOracle of delistedOracles) {
29
+ for (const spotMarket of spotMarkets) {
30
+ if (!spotMarket.data) {
31
+ continue;
32
+ }
33
+
34
+ if (spotMarket.data.oracle.equals(delistedOracle)) {
35
+ break;
36
+ }
37
+ }
38
+ filteredDelistedOracles.push(delistedOracle);
39
+ }
40
+
41
+ return {
42
+ perpMarketIndexes: delistedPerpMarketIndexes,
43
+ oracles: filteredDelistedOracles,
44
+ };
45
+ }
@@ -1,19 +1,21 @@
1
1
  import {
2
- DriftClientAccountSubscriber,
3
- DriftClientAccountEvents,
2
+ AccountSubscriber,
4
3
  DataAndSlot,
4
+ DelistedMarketSetting,
5
+ DriftClientAccountEvents,
6
+ DriftClientAccountSubscriber,
7
+ NotSubscribedError,
5
8
  ResubOpts,
6
9
  } from './types';
7
- import { AccountSubscriber, NotSubscribedError } from './types';
8
- import { SpotMarketAccount, PerpMarketAccount, StateAccount } from '../types';
10
+ import { PerpMarketAccount, SpotMarketAccount, StateAccount } from '../types';
9
11
  import { Program } from '@coral-xyz/anchor';
10
12
  import StrictEventEmitter from 'strict-event-emitter-types';
11
13
  import { EventEmitter } from 'events';
12
14
  import {
13
15
  getDriftStateAccountPublicKey,
14
- getSpotMarketPublicKey,
15
16
  getPerpMarketPublicKey,
16
17
  getPerpMarketPublicKeySync,
18
+ getSpotMarketPublicKey,
17
19
  getSpotMarketPublicKeySync,
18
20
  } from '../addresses/pda';
19
21
  import { WebSocketAccountSubscriber } from './webSocketAccountSubscriber';
@@ -23,6 +25,7 @@ import { OracleClientCache } from '../oracles/oracleClientCache';
23
25
  import * as Buffer from 'buffer';
24
26
  import { QUOTE_ORACLE_PRICE_DATA } from '../oracles/quoteAssetOracleClient';
25
27
  import { findAllMarketAndOracles } from '../config';
28
+ import { findDelistedPerpMarketsAndOracles } from './utils';
26
29
 
27
30
  const ORACLE_DEFAULT_KEY = PublicKey.default.toBase58();
28
31
 
@@ -55,6 +58,7 @@ export class WebSocketDriftClientAccountSubscriber
55
58
  spotOracleMap = new Map<number, PublicKey>();
56
59
  spotOracleStringMap = new Map<number, string>();
57
60
  oracleSubscribers = new Map<string, AccountSubscriber<OraclePriceData>>();
61
+ delistedMarketSetting: DelistedMarketSetting;
58
62
 
59
63
  initialPerpMarketAccountData: Map<number, PerpMarketAccount>;
60
64
  initialSpotMarketAccountData: Map<number, SpotMarketAccount>;
@@ -70,6 +74,7 @@ export class WebSocketDriftClientAccountSubscriber
70
74
  spotMarketIndexes: number[],
71
75
  oracleInfos: OracleInfo[],
72
76
  shouldFindAllMarketsAndOracles: boolean,
77
+ delistedMarketSetting: DelistedMarketSetting,
73
78
  resubOpts?: ResubOpts,
74
79
  commitment?: Commitment
75
80
  ) {
@@ -80,6 +85,7 @@ export class WebSocketDriftClientAccountSubscriber
80
85
  this.spotMarketIndexes = spotMarketIndexes;
81
86
  this.oracleInfos = oracleInfos;
82
87
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
88
+ this.delistedMarketSetting = delistedMarketSetting;
83
89
  this.resubOpts = resubOpts;
84
90
  this.commitment = commitment;
85
91
  }
@@ -151,6 +157,8 @@ export class WebSocketDriftClientAccountSubscriber
151
157
 
152
158
  this.eventEmitter.emit('update');
153
159
 
160
+ await this.handleDelistedMarkets();
161
+
154
162
  await Promise.all([this.setPerpOracleMap(), this.setSpotOracleMap()]);
155
163
 
156
164
  this.isSubscribing = false;
@@ -480,6 +488,33 @@ export class WebSocketDriftClientAccountSubscriber
480
488
  await Promise.all(addOraclePromises);
481
489
  }
482
490
 
491
+ async handleDelistedMarkets(): Promise<void> {
492
+ if (this.delistedMarketSetting === DelistedMarketSetting.Subscribe) {
493
+ return;
494
+ }
495
+
496
+ const { perpMarketIndexes, oracles } = findDelistedPerpMarketsAndOracles(
497
+ this.getMarketAccountsAndSlots(),
498
+ this.getSpotMarketAccountsAndSlots()
499
+ );
500
+
501
+ for (const perpMarketIndex of perpMarketIndexes) {
502
+ await this.perpMarketAccountSubscribers
503
+ .get(perpMarketIndex)
504
+ .unsubscribe();
505
+ if (this.delistedMarketSetting === DelistedMarketSetting.Discard) {
506
+ this.perpMarketAccountSubscribers.delete(perpMarketIndex);
507
+ }
508
+ }
509
+
510
+ for (const oracle of oracles) {
511
+ await this.oracleSubscribers.get(oracle.toBase58()).unsubscribe();
512
+ if (this.delistedMarketSetting === DelistedMarketSetting.Discard) {
513
+ this.oracleSubscribers.delete(oracle.toBase58());
514
+ }
515
+ }
516
+ }
517
+
483
518
  assertIsSubscribed(): void {
484
519
  if (!this.isSubscribed) {
485
520
  throw new NotSubscribedError(
package/src/config.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { PerpMarketAccount, SpotMarketAccount } from '.';
1
+ import { ConfirmOptions } from '@solana/web3.js';
2
+ import { PerpMarketAccount, PublicKey, SpotMarketAccount } from '.';
2
3
  import {
3
4
  DevnetPerpMarkets,
4
5
  MainnetPerpMarkets,
@@ -13,6 +14,10 @@ import {
13
14
  } from './constants/spotMarkets';
14
15
  import { OracleInfo } from './oracles/types';
15
16
  import { Program, ProgramAccount } from '@coral-xyz/anchor';
17
+ import {
18
+ ON_DEMAND_DEVNET_PID,
19
+ ON_DEMAND_MAINNET_PID,
20
+ } from '@switchboard-xyz/on-demand';
16
21
 
17
22
  type DriftConfig = {
18
23
  ENV: DriftEnv;
@@ -30,6 +35,7 @@ type DriftConfig = {
30
35
  MARKET_LOOKUP_TABLE: string;
31
36
  SERUM_LOOKUP_TABLE?: string;
32
37
  PYTH_PULL_ORACLE_LOOKUP_TABLE?: string;
38
+ SB_ON_DEMAND_PID: PublicKey;
33
39
  };
34
40
 
35
41
  export type DriftEnv = 'devnet' | 'mainnet-beta';
@@ -37,6 +43,14 @@ export type DriftEnv = 'devnet' | 'mainnet-beta';
37
43
  export const DRIFT_PROGRAM_ID = 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
38
44
  export const DRIFT_ORACLE_RECEIVER_ID =
39
45
  'G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha';
46
+ export const SWIFT_ID = 'SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS';
47
+ export const ANCHOR_TEST_SWIFT_ID =
48
+ 'DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv';
49
+
50
+ export const DEFAULT_CONFIRMATION_OPTS: ConfirmOptions = {
51
+ preflightCommitment: 'confirmed',
52
+ commitment: 'confirmed',
53
+ };
40
54
 
41
55
  export const configs: { [key in DriftEnv]: DriftConfig } = {
42
56
  devnet: {
@@ -54,6 +68,7 @@ export const configs: { [key in DriftEnv]: DriftConfig } = {
54
68
  SPOT_MARKETS: DevnetSpotMarkets,
55
69
  MARKET_LOOKUP_TABLE: 'FaMS3U4uBojvGn5FSDEPimddcXsCfwkKsFgMVVnDdxGb',
56
70
  DRIFT_ORACLE_RECEIVER_ID,
71
+ SB_ON_DEMAND_PID: ON_DEMAND_DEVNET_PID,
57
72
  },
58
73
  'mainnet-beta': {
59
74
  ENV: 'mainnet-beta',
@@ -71,6 +86,7 @@ export const configs: { [key in DriftEnv]: DriftConfig } = {
71
86
  MARKET_LOOKUP_TABLE: 'D9cnvzswDikQDf53k4HpQ3KJ9y1Fv3HGGDFYMXnK5T6c',
72
87
  SERUM_LOOKUP_TABLE: 'GPZkp76cJtNL2mphCvT6FXkJCVPpouidnacckR6rzKDN',
73
88
  DRIFT_ORACLE_RECEIVER_ID,
89
+ SB_ON_DEMAND_PID: ON_DEMAND_MAINNET_PID,
74
90
  },
75
91
  };
76
92
 
@@ -297,7 +297,7 @@ export const DevnetPerpMarkets: PerpMarketConfig[] = [
297
297
  symbol: 'W-PERP',
298
298
  baseAssetSymbol: 'W',
299
299
  marketIndex: 23,
300
- oracle: new PublicKey('4iCi4DvXrubHQne8jzbMaWL3pd7v1Fip8iTe4H9vHNXB'),
300
+ oracle: new PublicKey('J9nrFWjDUeDVZ4BhhxsbQXWgLcLEgQyNBrCbwSADmJdr'),
301
301
  launchTs: 1709852537000,
302
302
  oracleSource: OracleSource.SWITCHBOARD_ON_DEMAND,
303
303
  pythFeedId:
@@ -862,6 +862,40 @@ export const MainnetPerpMarkets: PerpMarketConfig[] = [
862
862
  launchTs: 1726646453000,
863
863
  oracleSource: OracleSource.Prelaunch,
864
864
  },
865
+ {
866
+ fullName: 'MOTHER',
867
+ category: ['Solana', 'Meme'],
868
+ symbol: 'MOTHER-PERP',
869
+ baseAssetSymbol: 'MOTHER',
870
+ marketIndex: 44,
871
+ oracle: new PublicKey('56ap2coZG7FPWUigVm9XrpQs3xuCwnwQaWtjWZcffEUG'),
872
+ launchTs: 1727291859000,
873
+ oracleSource: OracleSource.PYTH_PULL,
874
+ pythFeedId:
875
+ '0x62742a997d01f7524f791fdb2dd43aaf0e567d765ebf8fd0406a994239e874d4',
876
+ },
877
+ {
878
+ fullName: 'MOODENG',
879
+ category: ['Solana', 'Meme'],
880
+ symbol: 'MOODENG-PERP',
881
+ baseAssetSymbol: 'MOODENG',
882
+ marketIndex: 45,
883
+ oracle: new PublicKey('21gjgEcuDppthwV16J1QpFzje3vmgMp2uSzh7pJsG7ob'),
884
+ launchTs: 1727965864000,
885
+ oracleSource: OracleSource.PYTH_PULL,
886
+ pythFeedId:
887
+ '0xffff73128917a90950cd0473fd2551d7cd274fd5a6cc45641881bbcc6ee73417',
888
+ },
889
+ {
890
+ fullName: 'WARWICK-FIGHT-WIN-BET',
891
+ category: ['Prediction', 'Sport'],
892
+ symbol: 'WARWICK-FIGHT-WIN-BET',
893
+ baseAssetSymbol: 'WARWICK-FIGHT-WIN',
894
+ marketIndex: 46,
895
+ oracle: new PublicKey('Dz5Nvxo1hv7Zfyu11hy8e97twLMRKk6heTWCDGXytj7N'),
896
+ launchTs: 1727965864000,
897
+ oracleSource: OracleSource.Prelaunch,
898
+ },
865
899
  ];
866
900
 
867
901
  export const PerpMarkets: { [key in DriftEnv]: PerpMarketConfig[] } = {
@@ -425,6 +425,17 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
425
425
  pythFeedId:
426
426
  '0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
427
427
  },
428
+ {
429
+ symbol: 'MOTHER',
430
+ marketIndex: 26,
431
+ oracle: new PublicKey('56ap2coZG7FPWUigVm9XrpQs3xuCwnwQaWtjWZcffEUG'),
432
+ oracleSource: OracleSource.PYTH_PULL,
433
+ mint: new PublicKey('3S8qX1MsMqRbiwKg2cQyx7nis1oHMgaCuc9c4VfvVdPN'),
434
+ precision: new BN(10).pow(SIX),
435
+ precisionExp: SIX,
436
+ pythFeedId:
437
+ '0x62742a997d01f7524f791fdb2dd43aaf0e567d765ebf8fd0406a994239e874d4',
438
+ },
428
439
  ];
429
440
 
430
441
  export const SpotMarkets: { [key in DriftEnv]: SpotMarketConfig[] } = {
@@ -0,0 +1 @@
1
+ export const NOT_CONFIRMED_ERROR_CODE = -1001;