@drift-labs/sdk 2.96.0-beta.25 → 2.96.0-beta.26

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.96.0-beta.25
1
+ 2.96.0-beta.26
package/lib/config.d.ts CHANGED
@@ -26,6 +26,7 @@ export type DriftEnv = 'devnet' | 'mainnet-beta';
26
26
  export declare const DRIFT_PROGRAM_ID = "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH";
27
27
  export declare const DRIFT_ORACLE_RECEIVER_ID = "G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha";
28
28
  export declare const SWIFT_ID = "SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS";
29
+ export declare const ANCHOR_TEST_SWIFT_ID = "DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv";
29
30
  export declare const DEFAULT_CONFIRMATION_OPTS: ConfirmOptions;
30
31
  export declare const configs: {
31
32
  [key in DriftEnv]: DriftConfig;
package/lib/config.js CHANGED
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findAllMarketAndOracles = exports.getMarketsAndOraclesForSubscription = exports.initialize = exports.getConfig = exports.configs = exports.DEFAULT_CONFIRMATION_OPTS = exports.SWIFT_ID = exports.DRIFT_ORACLE_RECEIVER_ID = exports.DRIFT_PROGRAM_ID = void 0;
3
+ exports.findAllMarketAndOracles = exports.getMarketsAndOraclesForSubscription = exports.initialize = exports.getConfig = exports.configs = exports.DEFAULT_CONFIRMATION_OPTS = exports.ANCHOR_TEST_SWIFT_ID = exports.SWIFT_ID = exports.DRIFT_ORACLE_RECEIVER_ID = exports.DRIFT_PROGRAM_ID = void 0;
4
4
  const perpMarkets_1 = require("./constants/perpMarkets");
5
5
  const spotMarkets_1 = require("./constants/spotMarkets");
6
6
  const on_demand_1 = require("@switchboard-xyz/on-demand");
7
7
  exports.DRIFT_PROGRAM_ID = 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
8
8
  exports.DRIFT_ORACLE_RECEIVER_ID = 'G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha';
9
9
  exports.SWIFT_ID = 'SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS';
10
+ exports.ANCHOR_TEST_SWIFT_ID = 'DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv';
10
11
  exports.DEFAULT_CONFIRMATION_OPTS = {
11
12
  preflightCommitment: 'confirmed',
12
13
  commitment: 'confirmed',
@@ -263,7 +263,7 @@ exports.DevnetPerpMarkets = [
263
263
  symbol: 'W-PERP',
264
264
  baseAssetSymbol: 'W',
265
265
  marketIndex: 23,
266
- oracle: new web3_js_1.PublicKey('4iCi4DvXrubHQne8jzbMaWL3pd7v1Fip8iTe4H9vHNXB'),
266
+ oracle: new web3_js_1.PublicKey('J9nrFWjDUeDVZ4BhhxsbQXWgLcLEgQyNBrCbwSADmJdr'),
267
267
  launchTs: 1709852537000,
268
268
  oracleSource: __1.OracleSource.SWITCHBOARD_ON_DEMAND,
269
269
  pythFeedId: '0xeff7446475e218517566ea99e72a4abec2e1bd8498b43b7d8331e29dcb059389',
@@ -0,0 +1 @@
1
+ export declare const NOT_CONFIRMED_ERROR_CODE = -1001;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NOT_CONFIRMED_ERROR_CODE = void 0;
4
+ exports.NOT_CONFIRMED_ERROR_CODE = -1001;
@@ -37,6 +37,7 @@ export declare class DriftClient {
37
37
  connection: Connection;
38
38
  wallet: IWallet;
39
39
  program: Program;
40
+ swiftID: PublicKey;
40
41
  provider: AnchorProvider;
41
42
  opts?: ConfirmOptions;
42
43
  users: Map<string, User>;
@@ -80,7 +80,7 @@ class DriftClient {
80
80
  this._isSubscribed = val;
81
81
  }
82
82
  constructor(config) {
83
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7;
83
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8;
84
84
  this.users = new Map();
85
85
  this._isSubscribed = false;
86
86
  this.perpMarketLastSlotCache = new Map();
@@ -98,16 +98,17 @@ class DriftClient {
98
98
  // @ts-ignore
99
99
  config.wallet, this.opts);
100
100
  this.program = new anchor_1.Program(drift_json_1.default, (_c = config.programID) !== null && _c !== void 0 ? _c : new web3_js_1.PublicKey(config_1.DRIFT_PROGRAM_ID), this.provider);
101
- this.authority = (_d = config.authority) !== null && _d !== void 0 ? _d : this.wallet.publicKey;
102
- this.activeSubAccountId = (_e = config.activeSubAccountId) !== null && _e !== void 0 ? _e : 0;
103
- this.skipLoadUsers = (_f = config.skipLoadUsers) !== null && _f !== void 0 ? _f : false;
104
- this.txVersion = (_g = config.txVersion) !== null && _g !== void 0 ? _g : 'legacy';
101
+ this.swiftID = (_d = config.swiftID) !== null && _d !== void 0 ? _d : new web3_js_1.PublicKey(config_1.SWIFT_ID);
102
+ this.authority = (_e = config.authority) !== null && _e !== void 0 ? _e : this.wallet.publicKey;
103
+ this.activeSubAccountId = (_f = config.activeSubAccountId) !== null && _f !== void 0 ? _f : 0;
104
+ this.skipLoadUsers = (_g = config.skipLoadUsers) !== null && _g !== void 0 ? _g : false;
105
+ this.txVersion = (_h = config.txVersion) !== null && _h !== void 0 ? _h : 'legacy';
105
106
  this.txParams = {
106
- computeUnits: (_j = (_h = config.txParams) === null || _h === void 0 ? void 0 : _h.computeUnits) !== null && _j !== void 0 ? _j : 600000,
107
- computeUnitsPrice: (_l = (_k = config.txParams) === null || _k === void 0 ? void 0 : _k.computeUnitsPrice) !== null && _l !== void 0 ? _l : 0,
107
+ computeUnits: (_k = (_j = config.txParams) === null || _j === void 0 ? void 0 : _j.computeUnits) !== null && _k !== void 0 ? _k : 600000,
108
+ computeUnitsPrice: (_m = (_l = config.txParams) === null || _l === void 0 ? void 0 : _l.computeUnitsPrice) !== null && _m !== void 0 ? _m : 0,
108
109
  };
109
110
  this.txHandler =
110
- (_m = config === null || config === void 0 ? void 0 : config.txHandler) !== null && _m !== void 0 ? _m : new txHandler_1.TxHandler({
111
+ (_o = config === null || config === void 0 ? void 0 : config.txHandler) !== null && _o !== void 0 ? _o : new txHandler_1.TxHandler({
111
112
  connection: this.connection,
112
113
  // @ts-ignore
113
114
  wallet: this.provider.wallet,
@@ -133,8 +134,8 @@ class DriftClient {
133
134
  : config.subAccountIds
134
135
  ? new Map([[this.authority.toString(), config.subAccountIds]])
135
136
  : new Map();
136
- this.includeDelegates = (_o = config.includeDelegates) !== null && _o !== void 0 ? _o : false;
137
- if (((_p = config.accountSubscription) === null || _p === void 0 ? void 0 : _p.type) === 'polling') {
137
+ this.includeDelegates = (_p = config.includeDelegates) !== null && _p !== void 0 ? _p : false;
138
+ if (((_q = config.accountSubscription) === null || _q === void 0 ? void 0 : _q.type) === 'polling') {
138
139
  this.userAccountSubscriptionConfig = {
139
140
  type: 'polling',
140
141
  accountLoader: config.accountSubscription.accountLoader,
@@ -147,15 +148,15 @@ class DriftClient {
147
148
  else {
148
149
  this.userAccountSubscriptionConfig = {
149
150
  type: 'websocket',
150
- resubTimeoutMs: (_q = config.accountSubscription) === null || _q === void 0 ? void 0 : _q.resubTimeoutMs,
151
- logResubMessages: (_r = config.accountSubscription) === null || _r === void 0 ? void 0 : _r.logResubMessages,
152
- commitment: (_s = config.accountSubscription) === null || _s === void 0 ? void 0 : _s.commitment,
151
+ resubTimeoutMs: (_r = config.accountSubscription) === null || _r === void 0 ? void 0 : _r.resubTimeoutMs,
152
+ logResubMessages: (_s = config.accountSubscription) === null || _s === void 0 ? void 0 : _s.logResubMessages,
153
+ commitment: (_t = config.accountSubscription) === null || _t === void 0 ? void 0 : _t.commitment,
153
154
  };
154
155
  this.userStatsAccountSubscriptionConfig = {
155
156
  type: 'websocket',
156
- resubTimeoutMs: (_t = config.accountSubscription) === null || _t === void 0 ? void 0 : _t.resubTimeoutMs,
157
- logResubMessages: (_u = config.accountSubscription) === null || _u === void 0 ? void 0 : _u.logResubMessages,
158
- commitment: (_v = config.accountSubscription) === null || _v === void 0 ? void 0 : _v.commitment,
157
+ resubTimeoutMs: (_u = config.accountSubscription) === null || _u === void 0 ? void 0 : _u.resubTimeoutMs,
158
+ logResubMessages: (_v = config.accountSubscription) === null || _v === void 0 ? void 0 : _v.logResubMessages,
159
+ commitment: (_w = config.accountSubscription) === null || _w === void 0 ? void 0 : _w.commitment,
159
160
  };
160
161
  }
161
162
  if (config.userStats) {
@@ -173,14 +174,14 @@ class DriftClient {
173
174
  const noMarketsAndOraclesSpecified = config.perpMarketIndexes === undefined &&
174
175
  config.spotMarketIndexes === undefined &&
175
176
  config.oracleInfos === undefined;
176
- if (((_w = config.accountSubscription) === null || _w === void 0 ? void 0 : _w.type) === 'polling') {
177
- this.accountSubscriber = new pollingDriftClientAccountSubscriber_1.PollingDriftClientAccountSubscriber(this.program, config.accountSubscription.accountLoader, (_x = config.perpMarketIndexes) !== null && _x !== void 0 ? _x : [], (_y = config.spotMarketIndexes) !== null && _y !== void 0 ? _y : [], (_z = config.oracleInfos) !== null && _z !== void 0 ? _z : [], noMarketsAndOraclesSpecified, delistedMarketSetting);
177
+ if (((_x = config.accountSubscription) === null || _x === void 0 ? void 0 : _x.type) === 'polling') {
178
+ this.accountSubscriber = new pollingDriftClientAccountSubscriber_1.PollingDriftClientAccountSubscriber(this.program, config.accountSubscription.accountLoader, (_y = config.perpMarketIndexes) !== null && _y !== void 0 ? _y : [], (_z = config.spotMarketIndexes) !== null && _z !== void 0 ? _z : [], (_0 = config.oracleInfos) !== null && _0 !== void 0 ? _0 : [], noMarketsAndOraclesSpecified, delistedMarketSetting);
178
179
  }
179
180
  else {
180
- this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_0 = config.perpMarketIndexes) !== null && _0 !== void 0 ? _0 : [], (_1 = config.spotMarketIndexes) !== null && _1 !== void 0 ? _1 : [], (_2 = config.oracleInfos) !== null && _2 !== void 0 ? _2 : [], noMarketsAndOraclesSpecified, delistedMarketSetting, {
181
- resubTimeoutMs: (_3 = config.accountSubscription) === null || _3 === void 0 ? void 0 : _3.resubTimeoutMs,
182
- logResubMessages: (_4 = config.accountSubscription) === null || _4 === void 0 ? void 0 : _4.logResubMessages,
183
- }, (_5 = config.accountSubscription) === null || _5 === void 0 ? void 0 : _5.commitment);
181
+ this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_1 = config.perpMarketIndexes) !== null && _1 !== void 0 ? _1 : [], (_2 = config.spotMarketIndexes) !== null && _2 !== void 0 ? _2 : [], (_3 = config.oracleInfos) !== null && _3 !== void 0 ? _3 : [], noMarketsAndOraclesSpecified, delistedMarketSetting, {
182
+ resubTimeoutMs: (_4 = config.accountSubscription) === null || _4 === void 0 ? void 0 : _4.resubTimeoutMs,
183
+ logResubMessages: (_5 = config.accountSubscription) === null || _5 === void 0 ? void 0 : _5.logResubMessages,
184
+ }, (_6 = config.accountSubscription) === null || _6 === void 0 ? void 0 : _6.commitment);
184
185
  }
185
186
  this.eventEmitter = this.accountSubscriber.eventEmitter;
186
187
  this.metricsEventEmitter = new events_1.EventEmitter();
@@ -188,14 +189,14 @@ class DriftClient {
188
189
  this.enableMetricsEvents = true;
189
190
  }
190
191
  this.txSender =
191
- (_6 = config.txSender) !== null && _6 !== void 0 ? _6 : new retryTxSender_1.RetryTxSender({
192
+ (_7 = config.txSender) !== null && _7 !== void 0 ? _7 : new retryTxSender_1.RetryTxSender({
192
193
  connection: this.connection,
193
194
  wallet: this.wallet,
194
195
  opts: this.opts,
195
196
  txHandler: this.txHandler,
196
197
  });
197
198
  this.sbOnDemandProgramdId =
198
- config_1.configs[(_7 = config.env) !== null && _7 !== void 0 ? _7 : 'mainnet-beta'].SB_ON_DEMAND_PID;
199
+ config_1.configs[(_8 = config.env) !== null && _8 !== void 0 ? _8 : 'mainnet-beta'].SB_ON_DEMAND_PID;
199
200
  }
200
201
  getUserMapKey(subAccountId, authority) {
201
202
  return `${subAccountId}_${authority.toString()}`;
@@ -2956,7 +2957,7 @@ class DriftClient {
2956
2957
  readablePerpMarketIndex: marketIndex,
2957
2958
  });
2958
2959
  const swiftServerSignatureIx = web3_js_1.Ed25519Program.createInstructionWithPublicKey({
2959
- publicKey: new web3_js_1.PublicKey(config_1.SWIFT_ID).toBytes(),
2960
+ publicKey: new web3_js_1.PublicKey(this.swiftID).toBytes(),
2960
2961
  signature: Uint8Array.from(swiftSignature),
2961
2962
  message: Uint8Array.from(encodedSwiftServerMessage),
2962
2963
  });
@@ -11,6 +11,7 @@ export type DriftClientConfig = {
11
11
  wallet: IWallet;
12
12
  env?: DriftEnv;
13
13
  programID?: PublicKey;
14
+ swiftID?: PublicKey;
14
15
  accountSubscription?: DriftClientSubscriptionConfig;
15
16
  opts?: ConfirmOptions;
16
17
  txSender?: TxSender;
@@ -48,7 +48,6 @@ export declare abstract class BaseTxSender implements TxSender {
48
48
  addAdditionalConnection(newConnection: Connection): void;
49
49
  getTimeoutCount(): number;
50
50
  checkConfirmationResultForError(txSig: string, result: SignatureResult): Promise<void>;
51
- reportTransactionError(txSig: string): Promise<any>;
52
51
  getTxLandRate(): number;
53
52
  private defaultLandRateToFeeFunc;
54
53
  getSuggestedPriorityFeeMultiplier(): number;
@@ -5,16 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.BaseTxSender = void 0;
7
7
  const types_1 = require("./types");
8
- const web3_js_1 = require("@solana/web3.js");
9
8
  const assert_1 = __importDefault(require("assert"));
10
9
  const bs58_1 = __importDefault(require("bs58"));
11
10
  const txHandler_1 = require("./txHandler");
12
11
  const node_cache_1 = __importDefault(require("node-cache"));
13
12
  const config_1 = require("../config");
13
+ const txConstants_1 = require("../constants/txConstants");
14
+ const reportTransactionError_1 = require("./reportTransactionError");
14
15
  const BASELINE_TX_LAND_RATE = 0.9;
15
16
  const DEFAULT_TIMEOUT = 35000;
16
17
  const DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES = 10;
17
- const NOT_CONFIRMED_ERROR_CODE = -1001;
18
18
  class BaseTxSender {
19
19
  constructor({ connection, wallet, opts = config_1.DEFAULT_CONFIRMATION_OPTS, timeout = DEFAULT_TIMEOUT, additionalConnections = new Array(), confirmationStrategy = types_1.ConfirmationStrategy.Combo, additionalTxSenderCallbacks, trackTxLandRate, txHandler, txLandRateLookbackWindowMinutes = DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES, landRateToFeeFunc, }) {
20
20
  this.timeoutCount = 0;
@@ -166,7 +166,7 @@ class BaseTxSender {
166
166
  }
167
167
  this.timeoutCount += 1;
168
168
  const duration = (Date.now() - start) / 1000;
169
- throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, NOT_CONFIRMED_ERROR_CODE);
169
+ throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, txConstants_1.NOT_CONFIRMED_ERROR_CODE);
170
170
  }
171
171
  return response;
172
172
  }
@@ -192,7 +192,7 @@ class BaseTxSender {
192
192
  // Transaction not confirmed within 30 seconds
193
193
  this.timeoutCount += 1;
194
194
  const duration = (Date.now() - start) / 1000;
195
- throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, NOT_CONFIRMED_ERROR_CODE);
195
+ throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, txConstants_1.NOT_CONFIRMED_ERROR_CODE);
196
196
  }
197
197
  async confirmTransaction(signature, commitment) {
198
198
  if (this.confirmationStrategy === types_1.ConfirmationStrategy.WebSocket ||
@@ -243,30 +243,12 @@ class BaseTxSender {
243
243
  return this.timeoutCount;
244
244
  }
245
245
  async checkConfirmationResultForError(txSig, result) {
246
+ var _a;
246
247
  if (result.err) {
247
- await this.reportTransactionError(txSig);
248
+ await (0, reportTransactionError_1.throwTransactionError)(txSig, this.connection, (_a = this.opts) === null || _a === void 0 ? void 0 : _a.commitment);
248
249
  }
249
250
  return;
250
251
  }
251
- async reportTransactionError(txSig) {
252
- var _a, _b;
253
- const transactionResult = await this.connection.getTransaction(txSig, {
254
- maxSupportedTransactionVersion: 0,
255
- });
256
- if (!((_a = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _a === void 0 ? void 0 : _a.err)) {
257
- return undefined;
258
- }
259
- const logs = transactionResult.meta.logMessages;
260
- const lastLog = logs[logs.length - 1];
261
- const friendlyMessage = (_b = lastLog === null || lastLog === void 0 ? void 0 : lastLog.match(/(failed:) (.+)/)) === null || _b === void 0 ? void 0 : _b[2];
262
- // @ts-ignore
263
- throw new web3_js_1.SendTransactionError({
264
- action: 'send',
265
- signature: txSig,
266
- transactionMessage: `Transaction Failed${friendlyMessage ? `: ${friendlyMessage}` : ''}`,
267
- logs,
268
- });
269
- }
270
252
  getTxLandRate() {
271
253
  if (!this.trackTxLandRate) {
272
254
  console.warn('trackTxLandRate is false, returning default land rate of 0');
@@ -0,0 +1,20 @@
1
+ import { Commitment, Connection, SendTransactionError, VersionedTransactionResponse } from '@solana/web3.js';
2
+ /**
3
+ * THROWS if there is an error
4
+ *
5
+ * Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
6
+ * @param txSig
7
+ * @param connection
8
+ * @returns
9
+ */
10
+ export declare const throwTransactionError: (txSig: string, connection: Connection, commitment?: Commitment) => Promise<void>;
11
+ /**
12
+ * RETURNS an error if there is one
13
+ *
14
+ * Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
15
+ * @param txSig
16
+ * @param connection
17
+ * @returns
18
+ */
19
+ export declare const getTransactionErrorFromTxSig: (txSig: string, connection: Connection, commitment?: Commitment) => Promise<SendTransactionError>;
20
+ export declare const getTransactionError: (transactionResult: VersionedTransactionResponse) => SendTransactionError;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTransactionError = exports.getTransactionErrorFromTxSig = exports.throwTransactionError = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const config_1 = require("../config");
6
+ /**
7
+ * The new getTransaction method expects a Finality type instead of a Commitment type. The only options for Finality are 'confirmed' and 'finalized'.
8
+ * @param commitment
9
+ * @returns
10
+ */
11
+ const commitmentToFinality = (commitment) => {
12
+ switch (commitment) {
13
+ case 'confirmed':
14
+ return 'confirmed';
15
+ case 'finalized':
16
+ return 'finalized';
17
+ default:
18
+ throw new Error(`Invalid commitment when reporting transaction error. The commitment must be 'confirmed' or 'finalized' but was given '${commitment}'. If you're using this commitment for a specific reason, you may need to roll your own logic here.`);
19
+ }
20
+ };
21
+ const getTransactionResult = async (txSig, connection, commitment) => {
22
+ const finality = commitmentToFinality(commitment || connection.commitment || config_1.DEFAULT_CONFIRMATION_OPTS.commitment);
23
+ return await connection.getTransaction(txSig, {
24
+ maxSupportedTransactionVersion: 0,
25
+ commitment: finality,
26
+ });
27
+ };
28
+ const getTransactionResultWithRetry = async (txSig, connection, commitment) => {
29
+ const start = Date.now();
30
+ const retryTimeout = 3000; // Timeout after 3 seconds
31
+ const retryInterval = 800; // Retry with 800ms interval
32
+ const retryCount = 3; // Retry 3 times
33
+ let currentCount = 0;
34
+ let transactionResult = await getTransactionResult(txSig, connection, commitment);
35
+ // Retry 3 times or until timeout as long as we don't have a result yet
36
+ while (!transactionResult &&
37
+ Date.now() - start < retryTimeout &&
38
+ currentCount < retryCount) {
39
+ // Sleep for 1 second :: Do this first so that we don't run the first loop immediately after the initial fetch above
40
+ await new Promise((resolve) => setTimeout(resolve, retryInterval));
41
+ transactionResult = await getTransactionResult(txSig, connection, commitment);
42
+ currentCount++;
43
+ }
44
+ return transactionResult;
45
+ };
46
+ /**
47
+ * THROWS if there is an error
48
+ *
49
+ * Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
50
+ * @param txSig
51
+ * @param connection
52
+ * @returns
53
+ */
54
+ const throwTransactionError = async (txSig, connection, commitment) => {
55
+ const err = await (0, exports.getTransactionErrorFromTxSig)(txSig, connection, commitment);
56
+ if (err) {
57
+ throw err;
58
+ }
59
+ return;
60
+ };
61
+ exports.throwTransactionError = throwTransactionError;
62
+ /**
63
+ * RETURNS an error if there is one
64
+ *
65
+ * Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
66
+ * @param txSig
67
+ * @param connection
68
+ * @returns
69
+ */
70
+ const getTransactionErrorFromTxSig = async (txSig, connection, commitment) => {
71
+ var _a;
72
+ const transactionResult = await getTransactionResultWithRetry(txSig, connection, commitment);
73
+ if (!transactionResult) {
74
+ // Throw a generic error because we couldn't get the transaction result for the given txSig
75
+ return new web3_js_1.SendTransactionError({
76
+ action: 'send',
77
+ signature: txSig,
78
+ transactionMessage: `Transaction Failed`,
79
+ });
80
+ }
81
+ if (!((_a = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _a === void 0 ? void 0 : _a.err)) {
82
+ // Assume that the transaction was successful and we are here erroneously because we have a result with no error
83
+ return;
84
+ }
85
+ return (0, exports.getTransactionError)(transactionResult);
86
+ };
87
+ exports.getTransactionErrorFromTxSig = getTransactionErrorFromTxSig;
88
+ const getTransactionError = (transactionResult) => {
89
+ var _a, _b, _c, _d, _e, _f;
90
+ if (!((_a = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _a === void 0 ? void 0 : _a.err)) {
91
+ return;
92
+ }
93
+ const logs = (_c = (_b = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _b === void 0 ? void 0 : _b.logMessages) !== null && _c !== void 0 ? _c : ['No logs'];
94
+ const lastLog = logs[logs.length - 1];
95
+ const friendlyMessage = (_d = lastLog === null || lastLog === void 0 ? void 0 : lastLog.match(/(failed:) (.+)/)) === null || _d === void 0 ? void 0 : _d[2];
96
+ return new web3_js_1.SendTransactionError({
97
+ action: 'send',
98
+ signature: (_f = (_e = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.transaction) === null || _e === void 0 ? void 0 : _e.signatures) === null || _f === void 0 ? void 0 : _f[0],
99
+ transactionMessage: `Transaction Failed${friendlyMessage ? `: ${friendlyMessage}` : ''}`,
100
+ logs,
101
+ });
102
+ };
103
+ exports.getTransactionError = getTransactionError;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { TxSigAndSlot } from './types';
2
+ import { ConfirmationStrategy, TxSigAndSlot } from './types';
3
3
  import { ConfirmOptions, Connection, Signer, Transaction, VersionedTransaction } from '@solana/web3.js';
4
4
  import { BaseTxSender } from './baseTxSender';
5
5
  import { TxHandler } from './txHandler';
@@ -21,13 +21,14 @@ export declare class WhileValidTxSender extends BaseTxSender {
21
21
  }>;
22
22
  useBlockHeightOffset: boolean;
23
23
  private checkAndSetUseBlockHeightOffset;
24
- constructor({ connection, wallet, opts, retrySleep, additionalConnections, additionalTxSenderCallbacks, txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }: {
24
+ constructor({ connection, wallet, opts, retrySleep, additionalConnections, confirmationStrategy, additionalTxSenderCallbacks, txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }: {
25
25
  connection: Connection;
26
26
  wallet: IWallet;
27
27
  opts?: ConfirmOptions;
28
28
  retrySleep?: number;
29
29
  additionalConnections?: any;
30
30
  additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
31
+ confirmationStrategy: ConfirmationStrategy;
31
32
  txHandler?: TxHandler;
32
33
  trackTxLandRate?: boolean;
33
34
  txLandRateLookbackWindowMinutes?: number;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WhileValidTxSender = void 0;
7
+ const types_1 = require("./types");
7
8
  const web3_js_1 = require("@solana/web3.js");
8
9
  const baseTxSender_1 = require("./baseTxSender");
9
10
  const bs58_1 = __importDefault(require("bs58"));
@@ -29,7 +30,7 @@ class WhileValidTxSender extends baseTxSender_1.BaseTxSender {
29
30
  }
30
31
  });
31
32
  }
32
- constructor({ connection, wallet, opts = { ...config_1.DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 }, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), additionalTxSenderCallbacks = [], txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }) {
33
+ constructor({ connection, wallet, opts = { ...config_1.DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 }, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), confirmationStrategy = types_1.ConfirmationStrategy.Combo, additionalTxSenderCallbacks = [], txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }) {
33
34
  super({
34
35
  connection,
35
36
  wallet,
@@ -39,6 +40,7 @@ class WhileValidTxSender extends baseTxSender_1.BaseTxSender {
39
40
  txHandler,
40
41
  trackTxLandRate,
41
42
  txLandRateLookbackWindowMinutes,
43
+ confirmationStrategy,
42
44
  landRateToFeeFunc,
43
45
  });
44
46
  this.timoutCount = 0;
@@ -103,13 +105,11 @@ class WhileValidTxSender extends baseTxSender_1.BaseTxSender {
103
105
  }
104
106
  const txSig = bs58_1.default.encode(signedTx.signatures[0]);
105
107
  this.untilValid.set(txSig, latestBlockhash);
106
- console.debug(`preflight_commitment`, `sending_tx_with_preflight_commitment::${opts === null || opts === void 0 ? void 0 : opts.preflightCommitment}`);
107
108
  return this.sendRawTransaction(signedTx.serialize(), opts);
108
109
  }
109
110
  async sendRawTransaction(rawTransaction, opts) {
110
111
  var _a, _b, _c;
111
112
  const startTime = this.getTimestamp();
112
- console.debug(`preflight_commitment`, `sending_tx_with_preflight_commitment::${opts === null || opts === void 0 ? void 0 : opts.preflightCommitment}`);
113
113
  const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
114
114
  (_a = this.txSigCache) === null || _a === void 0 ? void 0 : _a.set(txid, false);
115
115
  this.sendToAdditionalConnections(rawTransaction, opts);
package/lib/types.d.ts CHANGED
@@ -1103,6 +1103,7 @@ export type SwiftServerMessage = {
1103
1103
  export type SwiftOrderParamsMessage = {
1104
1104
  swiftOrderParams: OptionalOrderParams;
1105
1105
  expectedOrderId: number;
1106
+ subAccountId: number;
1106
1107
  takeProfitOrderParams: SwiftTriggerOrderParams | null;
1107
1108
  stopLossOrderParams: SwiftTriggerOrderParams | null;
1108
1109
  };
@@ -1,4 +1,4 @@
1
- import { Connection, SignatureStatus, TransactionConfirmationStatus } from '@solana/web3.js';
1
+ import { Connection, RpcResponseAndContext, SignatureResult, SignatureStatus, TransactionConfirmationStatus } from '@solana/web3.js';
2
2
  /**
3
3
  * 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.
4
4
  */
@@ -7,8 +7,10 @@ export declare class TransactionConfirmationManager {
7
7
  private pendingConfirmations;
8
8
  private intervalId;
9
9
  constructor(connection: Connection);
10
- confirmTransaction(txSig: string, desiredConfirmationStatus?: TransactionConfirmationStatus, timeout?: number, pollInterval?: number, searchTransactionHistory?: boolean): Promise<SignatureStatus>;
10
+ confirmTransactionWebSocket(txSig: string, timeout?: number, desiredConfirmationStatus?: TransactionConfirmationStatus): Promise<RpcResponseAndContext<SignatureResult>>;
11
+ confirmTransactionPolling(txSig: string, desiredConfirmationStatus?: TransactionConfirmationStatus, timeout?: number, pollInterval?: number, searchTransactionHistory?: boolean): Promise<SignatureStatus>;
11
12
  private startConfirmationLoop;
12
13
  private checkPendingConfirmations;
14
+ private checkStatusMatchesDesiredConfirmationStatus;
13
15
  private checkTransactionStatuses;
14
16
  }
@@ -2,6 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TransactionConfirmationManager = void 0;
4
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");
5
9
  const confirmationStatusValues = {
6
10
  processed: 0,
7
11
  confirmed: 1,
@@ -16,7 +20,75 @@ class TransactionConfirmationManager {
16
20
  this.intervalId = null;
17
21
  this.connection = connection;
18
22
  }
19
- async confirmTransaction(txSig, desiredConfirmationStatus = config_1.DEFAULT_CONFIRMATION_OPTS.commitment, timeout = 30000, pollInterval = 1000, searchTransactionHistory = false) {
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) {
20
92
  // Interval must be > 400ms and a multiple of 100ms
21
93
  if (pollInterval < 400 || pollInterval % 100 !== 0) {
22
94
  throw new Error('Transaction confirmation polling interval must be at least 400ms and a multiple of 100ms');
@@ -60,6 +132,14 @@ class TransactionConfirmationManager {
60
132
  this.intervalId = null;
61
133
  }
62
134
  }
135
+ checkStatusMatchesDesiredConfirmationStatus(status, desiredConfirmationStatus) {
136
+ if (status.confirmationStatus &&
137
+ confirmationStatusValues[status.confirmationStatus] >=
138
+ confirmationStatusValues[desiredConfirmationStatus]) {
139
+ return true;
140
+ }
141
+ return false;
142
+ }
63
143
  async checkTransactionStatuses(requests) {
64
144
  const txSigs = requests.map((request) => request.txSig);
65
145
  const { value: statuses } = await this.connection.getSignatureStatuses(txSigs, {
@@ -75,8 +155,8 @@ class TransactionConfirmationManager {
75
155
  continue;
76
156
  }
77
157
  if (status.err) {
78
- request.reject(new Error(`Transaction failed: ${JSON.stringify(status.err)}`));
79
158
  this.pendingConfirmations.delete(request.txSig);
159
+ request.reject(await (0, reportTransactionError_1.getTransactionErrorFromTxSig)(request.txSig, this.connection));
80
160
  continue;
81
161
  }
82
162
  if (confirmationStatusValues[status.confirmationStatus] === undefined ||
@@ -84,9 +164,7 @@ class TransactionConfirmationManager {
84
164
  undefined) {
85
165
  throw new Error(`Invalid confirmation status when awaiting confirmation: ${status.confirmationStatus}`);
86
166
  }
87
- if (status.confirmationStatus &&
88
- confirmationStatusValues[status.confirmationStatus] >=
89
- confirmationStatusValues[request.desiredConfirmationStatus]) {
167
+ if (this.checkStatusMatchesDesiredConfirmationStatus(status, request.desiredConfirmationStatus)) {
90
168
  request.resolve(status);
91
169
  this.pendingConfirmations.delete(request.txSig);
92
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.96.0-beta.25",
3
+ "version": "2.96.0-beta.26",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
package/src/config.ts CHANGED
@@ -44,6 +44,8 @@ export const DRIFT_PROGRAM_ID = 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
44
44
  export const DRIFT_ORACLE_RECEIVER_ID =
45
45
  'G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha';
46
46
  export const SWIFT_ID = 'SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS';
47
+ export const ANCHOR_TEST_SWIFT_ID =
48
+ 'DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv';
47
49
 
48
50
  export const DEFAULT_CONFIRMATION_OPTS: ConfirmOptions = {
49
51
  preflightCommitment: 'confirmed',
@@ -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:
@@ -0,0 +1 @@
1
+ export const NOT_CONFIRMED_ERROR_CODE = -1001;
@@ -181,6 +181,7 @@ export class DriftClient {
181
181
  connection: Connection;
182
182
  wallet: IWallet;
183
183
  public program: Program;
184
+ public swiftID: PublicKey;
184
185
  provider: AnchorProvider;
185
186
  opts?: ConfirmOptions;
186
187
  users = new Map<string, User>();
@@ -245,6 +246,7 @@ export class DriftClient {
245
246
  config.programID ?? new PublicKey(DRIFT_PROGRAM_ID),
246
247
  this.provider
247
248
  );
249
+ this.swiftID = config.swiftID ?? new PublicKey(SWIFT_ID);
248
250
 
249
251
  this.authority = config.authority ?? this.wallet.publicKey;
250
252
  this.activeSubAccountId = config.activeSubAccountId ?? 0;
@@ -5483,7 +5485,7 @@ export class DriftClient {
5483
5485
 
5484
5486
  const swiftServerSignatureIx =
5485
5487
  Ed25519Program.createInstructionWithPublicKey({
5486
- publicKey: new PublicKey(SWIFT_ID).toBytes(),
5488
+ publicKey: new PublicKey(this.swiftID).toBytes(),
5487
5489
  signature: Uint8Array.from(swiftSignature),
5488
5490
  message: Uint8Array.from(encodedSwiftServerMessage),
5489
5491
  });
@@ -18,6 +18,7 @@ export type DriftClientConfig = {
18
18
  wallet: IWallet;
19
19
  env?: DriftEnv;
20
20
  programID?: PublicKey;
21
+ swiftID?: PublicKey;
21
22
  accountSubscription?: DriftClientSubscriptionConfig;
22
23
  opts?: ConfirmOptions;
23
24
  txSender?: TxSender;
@@ -15,7 +15,6 @@ import {
15
15
  TransactionSignature,
16
16
  Connection,
17
17
  VersionedTransaction,
18
- SendTransactionError,
19
18
  TransactionInstruction,
20
19
  AddressLookupTableAccount,
21
20
  BlockhashWithExpiryBlockHeight,
@@ -26,11 +25,12 @@ import { TxHandler } from './txHandler';
26
25
  import { IWallet } from '../types';
27
26
  import NodeCache from 'node-cache';
28
27
  import { DEFAULT_CONFIRMATION_OPTS } from '../config';
28
+ import { NOT_CONFIRMED_ERROR_CODE } from '../constants/txConstants';
29
+ import { throwTransactionError } from './reportTransactionError';
29
30
 
30
31
  const BASELINE_TX_LAND_RATE = 0.9;
31
32
  const DEFAULT_TIMEOUT = 35000;
32
33
  const DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES = 10;
33
- const NOT_CONFIRMED_ERROR_CODE = -1001;
34
34
 
35
35
  export abstract class BaseTxSender implements TxSender {
36
36
  connection: Connection;
@@ -406,40 +406,18 @@ export abstract class BaseTxSender implements TxSender {
406
406
  public async checkConfirmationResultForError(
407
407
  txSig: string,
408
408
  result: SignatureResult
409
- ) {
409
+ ): Promise<void> {
410
410
  if (result.err) {
411
- await this.reportTransactionError(txSig);
411
+ await throwTransactionError(
412
+ txSig,
413
+ this.connection,
414
+ this.opts?.commitment
415
+ );
412
416
  }
413
417
 
414
418
  return;
415
419
  }
416
420
 
417
- public async reportTransactionError(txSig: string) {
418
- const transactionResult = await this.connection.getTransaction(txSig, {
419
- maxSupportedTransactionVersion: 0,
420
- });
421
-
422
- if (!transactionResult?.meta?.err) {
423
- return undefined;
424
- }
425
-
426
- const logs = transactionResult.meta.logMessages;
427
-
428
- const lastLog = logs[logs.length - 1];
429
-
430
- const friendlyMessage = lastLog?.match(/(failed:) (.+)/)?.[2];
431
-
432
- // @ts-ignore
433
- throw new SendTransactionError({
434
- action: 'send',
435
- signature: txSig,
436
- transactionMessage: `Transaction Failed${
437
- friendlyMessage ? `: ${friendlyMessage}` : ''
438
- }`,
439
- logs,
440
- });
441
- }
442
-
443
421
  public getTxLandRate(): number {
444
422
  if (!this.trackTxLandRate) {
445
423
  console.warn(
@@ -0,0 +1,159 @@
1
+ import {
2
+ Commitment,
3
+ Connection,
4
+ Finality,
5
+ SendTransactionError,
6
+ VersionedTransactionResponse,
7
+ } from '@solana/web3.js';
8
+ import { DEFAULT_CONFIRMATION_OPTS } from '../config';
9
+
10
+ /**
11
+ * The new getTransaction method expects a Finality type instead of a Commitment type. The only options for Finality are 'confirmed' and 'finalized'.
12
+ * @param commitment
13
+ * @returns
14
+ */
15
+ const commitmentToFinality = (commitment: Commitment): Finality => {
16
+ switch (commitment) {
17
+ case 'confirmed':
18
+ return 'confirmed';
19
+ case 'finalized':
20
+ return 'finalized';
21
+ default:
22
+ throw new Error(
23
+ `Invalid commitment when reporting transaction error. The commitment must be 'confirmed' or 'finalized' but was given '${commitment}'. If you're using this commitment for a specific reason, you may need to roll your own logic here.`
24
+ );
25
+ }
26
+ };
27
+
28
+ const getTransactionResult = async (
29
+ txSig: string,
30
+ connection: Connection,
31
+ commitment?: Commitment
32
+ ): Promise<VersionedTransactionResponse> => {
33
+ const finality = commitmentToFinality(
34
+ commitment || connection.commitment || DEFAULT_CONFIRMATION_OPTS.commitment
35
+ );
36
+ return await connection.getTransaction(txSig, {
37
+ maxSupportedTransactionVersion: 0,
38
+ commitment: finality,
39
+ });
40
+ };
41
+
42
+ const getTransactionResultWithRetry = async (
43
+ txSig: string,
44
+ connection: Connection,
45
+ commitment?: Commitment
46
+ ): Promise<VersionedTransactionResponse> => {
47
+ const start = Date.now();
48
+
49
+ const retryTimeout = 3_000; // Timeout after 3 seconds
50
+ const retryInterval = 800; // Retry with 800ms interval
51
+ const retryCount = 3; // Retry 3 times
52
+
53
+ let currentCount = 0;
54
+ let transactionResult = await getTransactionResult(
55
+ txSig,
56
+ connection,
57
+ commitment
58
+ );
59
+
60
+ // Retry 3 times or until timeout as long as we don't have a result yet
61
+ while (
62
+ !transactionResult &&
63
+ Date.now() - start < retryTimeout &&
64
+ currentCount < retryCount
65
+ ) {
66
+ // Sleep for 1 second :: Do this first so that we don't run the first loop immediately after the initial fetch above
67
+ await new Promise((resolve) => setTimeout(resolve, retryInterval));
68
+
69
+ transactionResult = await getTransactionResult(
70
+ txSig,
71
+ connection,
72
+ commitment
73
+ );
74
+ currentCount++;
75
+ }
76
+
77
+ return transactionResult;
78
+ };
79
+
80
+ /**
81
+ * THROWS if there is an error
82
+ *
83
+ * Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
84
+ * @param txSig
85
+ * @param connection
86
+ * @returns
87
+ */
88
+ export const throwTransactionError = async (
89
+ txSig: string,
90
+ connection: Connection,
91
+ commitment?: Commitment
92
+ ): Promise<void> => {
93
+ const err = await getTransactionErrorFromTxSig(txSig, connection, commitment);
94
+
95
+ if (err) {
96
+ throw err;
97
+ }
98
+
99
+ return;
100
+ };
101
+
102
+ /**
103
+ * RETURNS an error if there is one
104
+ *
105
+ * Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
106
+ * @param txSig
107
+ * @param connection
108
+ * @returns
109
+ */
110
+ export const getTransactionErrorFromTxSig = async (
111
+ txSig: string,
112
+ connection: Connection,
113
+ commitment?: Commitment
114
+ ): Promise<SendTransactionError> => {
115
+ const transactionResult = await getTransactionResultWithRetry(
116
+ txSig,
117
+ connection,
118
+ commitment
119
+ );
120
+
121
+ if (!transactionResult) {
122
+ // Throw a generic error because we couldn't get the transaction result for the given txSig
123
+ return new SendTransactionError({
124
+ action: 'send',
125
+ signature: txSig,
126
+ transactionMessage: `Transaction Failed`,
127
+ });
128
+ }
129
+
130
+ if (!transactionResult?.meta?.err) {
131
+ // Assume that the transaction was successful and we are here erroneously because we have a result with no error
132
+ return;
133
+ }
134
+
135
+ return getTransactionError(transactionResult);
136
+ };
137
+
138
+ export const getTransactionError = (
139
+ transactionResult: VersionedTransactionResponse
140
+ ): SendTransactionError => {
141
+ if (!transactionResult?.meta?.err) {
142
+ return;
143
+ }
144
+
145
+ const logs = transactionResult?.meta?.logMessages ?? ['No logs'];
146
+
147
+ const lastLog = logs[logs.length - 1];
148
+
149
+ const friendlyMessage = lastLog?.match(/(failed:) (.+)/)?.[2];
150
+
151
+ return new SendTransactionError({
152
+ action: 'send',
153
+ signature: transactionResult?.transaction?.signatures?.[0],
154
+ transactionMessage: `Transaction Failed${
155
+ friendlyMessage ? `: ${friendlyMessage}` : ''
156
+ }`,
157
+ logs,
158
+ });
159
+ };
@@ -1,4 +1,4 @@
1
- import { TxSigAndSlot } from './types';
1
+ import { ConfirmationStrategy, TxSigAndSlot } from './types';
2
2
  import {
3
3
  ConfirmOptions,
4
4
  Connection,
@@ -62,6 +62,7 @@ export class WhileValidTxSender extends BaseTxSender {
62
62
  opts = { ...DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 },
63
63
  retrySleep = DEFAULT_RETRY,
64
64
  additionalConnections = new Array<Connection>(),
65
+ confirmationStrategy = ConfirmationStrategy.Combo,
65
66
  additionalTxSenderCallbacks = [],
66
67
  txHandler,
67
68
  trackTxLandRate,
@@ -74,6 +75,7 @@ export class WhileValidTxSender extends BaseTxSender {
74
75
  retrySleep?: number;
75
76
  additionalConnections?;
76
77
  additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
78
+ confirmationStrategy: ConfirmationStrategy;
77
79
  txHandler?: TxHandler;
78
80
  trackTxLandRate?: boolean;
79
81
  txLandRateLookbackWindowMinutes?: number;
@@ -88,6 +90,7 @@ export class WhileValidTxSender extends BaseTxSender {
88
90
  txHandler,
89
91
  trackTxLandRate,
90
92
  txLandRateLookbackWindowMinutes,
93
+ confirmationStrategy,
91
94
  landRateToFeeFunc,
92
95
  });
93
96
  this.retrySleep = retrySleep;
@@ -187,11 +190,6 @@ export class WhileValidTxSender extends BaseTxSender {
187
190
  const txSig = bs58.encode(signedTx.signatures[0]);
188
191
  this.untilValid.set(txSig, latestBlockhash);
189
192
 
190
- console.debug(
191
- `preflight_commitment`,
192
- `sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
193
- );
194
-
195
193
  return this.sendRawTransaction(signedTx.serialize(), opts);
196
194
  }
197
195
 
@@ -201,10 +199,6 @@ export class WhileValidTxSender extends BaseTxSender {
201
199
  ): Promise<TxSigAndSlot> {
202
200
  const startTime = this.getTimestamp();
203
201
 
204
- console.debug(
205
- `preflight_commitment`,
206
- `sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
207
- );
208
202
  const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
209
203
  this.txSigCache?.set(txid, false);
210
204
  this.sendToAdditionalConnections(rawTransaction, opts);
package/src/types.ts CHANGED
@@ -1064,6 +1064,7 @@ export type SwiftServerMessage = {
1064
1064
  export type SwiftOrderParamsMessage = {
1065
1065
  swiftOrderParams: OptionalOrderParams;
1066
1066
  expectedOrderId: number;
1067
+ subAccountId: number;
1067
1068
  takeProfitOrderParams: SwiftTriggerOrderParams | null;
1068
1069
  stopLossOrderParams: SwiftTriggerOrderParams | null;
1069
1070
  };
@@ -1,9 +1,24 @@
1
1
  import {
2
+ ClientSubscriptionId,
2
3
  Connection,
4
+ Context,
5
+ RpcResponseAndContext,
6
+ SignatureResult,
3
7
  SignatureStatus,
4
8
  TransactionConfirmationStatus,
5
9
  } from '@solana/web3.js';
6
10
  import { DEFAULT_CONFIRMATION_OPTS } from '../config';
11
+ import { TxSendError } from '..';
12
+ import { NOT_CONFIRMED_ERROR_CODE } from '../constants/txConstants';
13
+ import {
14
+ getTransactionErrorFromTxSig,
15
+ throwTransactionError,
16
+ } from '../tx/reportTransactionError';
17
+ import { promiseTimeout } from './promiseTimeout';
18
+
19
+ type ResolveReference = {
20
+ resolve?: () => void;
21
+ };
7
22
 
8
23
  const confirmationStatusValues: Record<TransactionConfirmationStatus, number> =
9
24
  {
@@ -36,7 +51,113 @@ export class TransactionConfirmationManager {
36
51
  this.connection = connection;
37
52
  }
38
53
 
39
- async confirmTransaction(
54
+ async confirmTransactionWebSocket(
55
+ txSig: string,
56
+ timeout = 30000,
57
+ desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus
58
+ ): Promise<RpcResponseAndContext<SignatureResult>> {
59
+ const start = Date.now();
60
+ const subscriptionCommitment =
61
+ desiredConfirmationStatus || DEFAULT_CONFIRMATION_OPTS.commitment;
62
+
63
+ let response: RpcResponseAndContext<SignatureResult> | null = null;
64
+
65
+ let subscriptionId: ClientSubscriptionId;
66
+
67
+ const confirmationPromise = new Promise((resolve, reject) => {
68
+ try {
69
+ subscriptionId = this.connection.onSignature(
70
+ txSig,
71
+ (result: SignatureResult, context: Context) => {
72
+ response = {
73
+ context,
74
+ value: result,
75
+ };
76
+ resolve(null);
77
+ },
78
+ subscriptionCommitment
79
+ );
80
+ } catch (err) {
81
+ reject(err);
82
+ }
83
+ });
84
+
85
+ // 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.
86
+ const oneShotConfirmationPromise = this.connection.getSignatureStatuses([
87
+ txSig,
88
+ ]);
89
+
90
+ const resolveReference: ResolveReference = {};
91
+
92
+ // 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.
93
+ const overallWaitingForConfirmationPromise = new Promise<void>(
94
+ (resolve) => {
95
+ resolveReference.resolve = resolve;
96
+ }
97
+ );
98
+
99
+ // Await for the one shot confirmation and resolve the waiting promise if we get a positive confirmation result
100
+ oneShotConfirmationPromise.then(
101
+ async (oneShotResponse) => {
102
+ if (!oneShotResponse || !oneShotResponse?.value?.[0]) return;
103
+
104
+ const resultValue = oneShotResponse.value[0];
105
+
106
+ if (resultValue.err) {
107
+ await throwTransactionError(txSig, this.connection);
108
+ }
109
+
110
+ if (
111
+ this.checkStatusMatchesDesiredConfirmationStatus(
112
+ resultValue,
113
+ desiredConfirmationStatus
114
+ )
115
+ ) {
116
+ response = {
117
+ context: oneShotResponse.context,
118
+ value: oneShotResponse.value[0],
119
+ };
120
+ resolveReference.resolve?.();
121
+ }
122
+ },
123
+ (onRejected) => {
124
+ throw onRejected;
125
+ }
126
+ );
127
+
128
+ // Await for the websocket confirmation with the configured timeout
129
+ promiseTimeout(confirmationPromise, timeout).then(
130
+ () => {
131
+ resolveReference.resolve?.();
132
+ },
133
+ (onRejected) => {
134
+ throw onRejected;
135
+ }
136
+ );
137
+
138
+ try {
139
+ await overallWaitingForConfirmationPromise;
140
+ } finally {
141
+ if (subscriptionId !== undefined) {
142
+ this.connection.removeSignatureListener(subscriptionId);
143
+ }
144
+ }
145
+
146
+ const duration = (Date.now() - start) / 1000;
147
+
148
+ if (response === null) {
149
+ throw new TxSendError(
150
+ `Transaction was not confirmed in ${duration.toFixed(
151
+ 2
152
+ )} seconds. It is unknown if it succeeded or failed. Check signature ${txSig} using the Solana Explorer or CLI tools.`,
153
+ NOT_CONFIRMED_ERROR_CODE
154
+ );
155
+ }
156
+
157
+ return response;
158
+ }
159
+
160
+ async confirmTransactionPolling(
40
161
  txSig: string,
41
162
  desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus,
42
163
  timeout = 30000,
@@ -99,6 +220,21 @@ export class TransactionConfirmationManager {
99
220
  }
100
221
  }
101
222
 
223
+ private checkStatusMatchesDesiredConfirmationStatus(
224
+ status: SignatureStatus,
225
+ desiredConfirmationStatus: TransactionConfirmationStatus
226
+ ): boolean {
227
+ if (
228
+ status.confirmationStatus &&
229
+ confirmationStatusValues[status.confirmationStatus] >=
230
+ confirmationStatusValues[desiredConfirmationStatus]
231
+ ) {
232
+ return true;
233
+ }
234
+
235
+ return false;
236
+ }
237
+
102
238
  private async checkTransactionStatuses(
103
239
  requests: TransactionConfirmationRequest[]
104
240
  ) {
@@ -125,10 +261,10 @@ export class TransactionConfirmationManager {
125
261
  }
126
262
 
127
263
  if (status.err) {
264
+ this.pendingConfirmations.delete(request.txSig);
128
265
  request.reject(
129
- new Error(`Transaction failed: ${JSON.stringify(status.err)}`)
266
+ await getTransactionErrorFromTxSig(request.txSig, this.connection)
130
267
  );
131
- this.pendingConfirmations.delete(request.txSig);
132
268
  continue;
133
269
  }
134
270
 
@@ -143,9 +279,10 @@ export class TransactionConfirmationManager {
143
279
  }
144
280
 
145
281
  if (
146
- status.confirmationStatus &&
147
- confirmationStatusValues[status.confirmationStatus] >=
148
- confirmationStatusValues[request.desiredConfirmationStatus]
282
+ this.checkStatusMatchesDesiredConfirmationStatus(
283
+ status,
284
+ request.desiredConfirmationStatus
285
+ )
149
286
  ) {
150
287
  request.resolve(status);
151
288
  this.pendingConfirmations.delete(request.txSig);
@@ -1,10 +1,14 @@
1
1
  import { expect } from 'chai';
2
2
  import sinon from 'sinon';
3
- import { Connection, SignatureStatus } from '@solana/web3.js';
3
+ import {
4
+ Connection,
5
+ SignatureStatus,
6
+ VersionedTransactionResponse,
7
+ } from '@solana/web3.js';
4
8
  import { TransactionConfirmationManager } from '../../src/util/TransactionConfirmationManager';
5
9
  import assert from 'assert';
6
10
 
7
- describe('TransactionConfirmationManager', () => {
11
+ describe('TransactionConfirmationManager_Polling_Tests', () => {
8
12
  let manager: TransactionConfirmationManager;
9
13
  let mockConnection: sinon.SinonStubbedInstance<Connection>;
10
14
 
@@ -21,7 +25,12 @@ describe('TransactionConfirmationManager', () => {
21
25
 
22
26
  it('should throw error for invalid poll interval', async () => {
23
27
  try {
24
- await manager.confirmTransaction('fakeTxSig', 'confirmed', 30000, 300);
28
+ await manager.confirmTransactionPolling(
29
+ 'fakeTxSig',
30
+ 'confirmed',
31
+ 30000,
32
+ 300
33
+ );
25
34
  assert.fail('Expected an error to be thrown');
26
35
  } catch (error) {
27
36
  assert(error instanceof Error);
@@ -46,7 +55,7 @@ describe('TransactionConfirmationManager', () => {
46
55
  value: [fakeStatus],
47
56
  });
48
57
 
49
- const result = await manager.confirmTransaction(
58
+ const result = await manager.confirmTransactionPolling(
50
59
  fakeTxSig,
51
60
  'confirmed',
52
61
  30000,
@@ -61,7 +70,7 @@ describe('TransactionConfirmationManager', () => {
61
70
  ).to.be.true;
62
71
  });
63
72
 
64
- it('should reject when transaction fails', async () => {
73
+ it('should reject when transaction fails', async function () {
65
74
  const fakeTxSig = 'fakeTxSig';
66
75
  const fakeStatus: SignatureStatus = {
67
76
  slot: 100,
@@ -75,15 +84,25 @@ describe('TransactionConfirmationManager', () => {
75
84
  value: [fakeStatus],
76
85
  });
77
86
 
87
+ // The transaction manager falls into getTransaction when it detects a transaction failure so we need to mock that as well
88
+ // @ts-ignore
89
+ mockConnection.getTransaction.resolves({
90
+ meta: {
91
+ logMessages: ['Transaction failed: Custom'],
92
+ err: { InstructionError: [0, 'Custom'] },
93
+ },
94
+ } as VersionedTransactionResponse);
95
+
78
96
  try {
79
- await manager.confirmTransaction(fakeTxSig, 'confirmed', 30000, 400);
97
+ await manager.confirmTransactionPolling(
98
+ fakeTxSig,
99
+ 'confirmed',
100
+ 30000,
101
+ 400
102
+ );
80
103
  assert.fail('Expected an error to be thrown');
81
104
  } catch (error) {
82
- assert(error instanceof Error);
83
- assert.strictEqual(
84
- error.message,
85
- 'Transaction failed: {"InstructionError":[0,"Custom"]}'
86
- );
105
+ return;
87
106
  }
88
107
  });
89
108
 
@@ -96,7 +115,7 @@ describe('TransactionConfirmationManager', () => {
96
115
  value: [null],
97
116
  });
98
117
 
99
- const promise = manager.confirmTransaction(
118
+ const promise = manager.confirmTransactionPolling(
100
119
  fakeTxSig,
101
120
  'confirmed',
102
121
  5000,
@@ -140,13 +159,13 @@ describe('TransactionConfirmationManager', () => {
140
159
  value: [fakeStatus1, fakeStatus2],
141
160
  });
142
161
 
143
- const promise1 = manager.confirmTransaction(
162
+ const promise1 = manager.confirmTransactionPolling(
144
163
  fakeTxSig1,
145
164
  'confirmed',
146
165
  30000,
147
166
  400
148
167
  );
149
- const promise2 = manager.confirmTransaction(
168
+ const promise2 = manager.confirmTransactionPolling(
150
169
  fakeTxSig2,
151
170
  'confirmed',
152
171
  30000,
@@ -206,13 +225,13 @@ describe('TransactionConfirmationManager', () => {
206
225
  const startTime = Date.now();
207
226
 
208
227
  // Start both confirmation processes
209
- const promise1 = manager.confirmTransaction(
228
+ const promise1 = manager.confirmTransactionPolling(
210
229
  fakeTxSig1,
211
230
  'confirmed',
212
231
  5000,
213
232
  400
214
233
  );
215
- const promise2 = manager.confirmTransaction(
234
+ const promise2 = manager.confirmTransactionPolling(
216
235
  fakeTxSig2,
217
236
  'confirmed',
218
237
  5000,