@drift-labs/sdk 2.96.0-beta.9 → 2.97.0-beta.0

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 +170 -2
  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 +170 -2
  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
@@ -1,22 +1,20 @@
1
- import { TxSigAndSlot } from './types';
1
+ import { ConfirmationStrategy, TxSigAndSlot } from './types';
2
2
  import {
3
- Commitment,
4
3
  ConfirmOptions,
5
4
  Connection,
5
+ SendTransactionError,
6
6
  Signer,
7
7
  Transaction,
8
8
  VersionedTransaction,
9
9
  } from '@solana/web3.js';
10
- import { AnchorProvider } from '@coral-xyz/anchor';
11
10
  import { BaseTxSender } from './baseTxSender';
12
11
  import bs58 from 'bs58';
13
12
  import { TxHandler } from './txHandler';
14
13
  import { IWallet } from '../types';
14
+ import { DEFAULT_CONFIRMATION_OPTS } from '../config';
15
15
 
16
16
  const DEFAULT_RETRY = 2000;
17
17
 
18
- const VALID_BLOCK_HEIGHT_OFFSET = -150; // This is a bit of weirdness but the lastValidBlockHeight value returned from connection.getLatestBlockhash is always 300 blocks ahead of the current block, even though the transaction actually expires after 150 blocks. This accounts for that so that we can at least accuractely estimate the transaction expiry.
19
-
20
18
  type ResolveReference = {
21
19
  resolve?: () => void;
22
20
  };
@@ -33,7 +31,6 @@ export class WhileValidTxSender extends BaseTxSender {
33
31
  string,
34
32
  { blockhash: string; lastValidBlockHeight: number }
35
33
  >();
36
- blockhashCommitment: Commitment;
37
34
 
38
35
  useBlockHeightOffset = true;
39
36
 
@@ -62,11 +59,11 @@ export class WhileValidTxSender extends BaseTxSender {
62
59
  public constructor({
63
60
  connection,
64
61
  wallet,
65
- opts = { ...AnchorProvider.defaultOptions(), maxRetries: 0 },
62
+ opts = { ...DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 },
66
63
  retrySleep = DEFAULT_RETRY,
67
64
  additionalConnections = new Array<Connection>(),
65
+ confirmationStrategy = ConfirmationStrategy.Combo,
68
66
  additionalTxSenderCallbacks = [],
69
- blockhashCommitment = 'finalized',
70
67
  txHandler,
71
68
  trackTxLandRate,
72
69
  txLandRateLookbackWindowMinutes,
@@ -78,7 +75,7 @@ export class WhileValidTxSender extends BaseTxSender {
78
75
  retrySleep?: number;
79
76
  additionalConnections?;
80
77
  additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
81
- blockhashCommitment?: Commitment;
78
+ confirmationStrategy?: ConfirmationStrategy;
82
79
  txHandler?: TxHandler;
83
80
  trackTxLandRate?: boolean;
84
81
  txLandRateLookbackWindowMinutes?: number;
@@ -93,10 +90,10 @@ export class WhileValidTxSender extends BaseTxSender {
93
90
  txHandler,
94
91
  trackTxLandRate,
95
92
  txLandRateLookbackWindowMinutes,
93
+ confirmationStrategy,
96
94
  landRateToFeeFunc,
97
95
  });
98
96
  this.retrySleep = retrySleep;
99
- this.blockhashCommitment = blockhashCommitment;
100
97
 
101
98
  this.checkAndSetUseBlockHeightOffset();
102
99
  }
@@ -139,7 +136,7 @@ export class WhileValidTxSender extends BaseTxSender {
139
136
 
140
137
  // handle subclass-specific side effects
141
138
  const txSig = bs58.encode(
142
- signedTx.signatures[0]?.signature || signedTx.signatures[0]
139
+ signedTx?.signature || signedTx.signatures[0]?.signature
143
140
  );
144
141
  this.untilValid.set(txSig, latestBlockhash);
145
142
 
@@ -234,27 +231,21 @@ export class WhileValidTxSender extends BaseTxSender {
234
231
 
235
232
  let slot: number;
236
233
  try {
237
- const { blockhash, lastValidBlockHeight } = this.untilValid.get(txid);
238
-
239
- const result = await this.connection.confirmTransaction(
240
- {
241
- signature: txid,
242
- blockhash,
243
- lastValidBlockHeight: this.useBlockHeightOffset
244
- ? lastValidBlockHeight + VALID_BLOCK_HEIGHT_OFFSET
245
- : lastValidBlockHeight,
246
- },
247
- opts?.commitment
248
- );
249
-
250
- if (!result) {
251
- throw new Error(`Couldn't get signature status for txid: ${txid}`);
252
- }
234
+ const result = await this.confirmTransaction(txid, opts.commitment);
253
235
 
254
236
  this.txSigCache?.set(txid, true);
255
237
 
256
238
  await this.checkConfirmationResultForError(txid, result.value);
257
239
 
240
+ if (result?.value?.err) {
241
+ // Fallback error handling if there's a problem reporting the error in checkConfirmationResultForError
242
+ throw new SendTransactionError({
243
+ action: 'send',
244
+ signature: txid,
245
+ transactionMessage: `Transaction Failed`,
246
+ });
247
+ }
248
+
258
249
  slot = result.context.slot;
259
250
  // eslint-disable-next-line no-useless-catch
260
251
  } catch (e) {
package/src/types.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js';
1
+ import {
2
+ Keypair,
3
+ PublicKey,
4
+ Transaction,
5
+ VersionedTransaction,
6
+ } from '@solana/web3.js';
2
7
  import { BN, ZERO } from '.';
3
8
 
4
9
  // Utility type which lets you denote record with values of type A mapped to a record with the same keys but values of type B
@@ -1051,6 +1056,24 @@ export const DefaultOrderParams: OrderParams = {
1051
1056
  auctionEndPrice: null,
1052
1057
  };
1053
1058
 
1059
+ export type SwiftServerMessage = {
1060
+ slot: BN;
1061
+ swiftOrderSignature: Uint8Array;
1062
+ };
1063
+
1064
+ export type SwiftOrderParamsMessage = {
1065
+ swiftOrderParams: OptionalOrderParams;
1066
+ expectedOrderId: number;
1067
+ subAccountId: number;
1068
+ takeProfitOrderParams: SwiftTriggerOrderParams | null;
1069
+ stopLossOrderParams: SwiftTriggerOrderParams | null;
1070
+ };
1071
+
1072
+ export type SwiftTriggerOrderParams = {
1073
+ triggerPrice: BN;
1074
+ baseAssetAmount: BN;
1075
+ };
1076
+
1054
1077
  export type MakerInfo = {
1055
1078
  maker: PublicKey;
1056
1079
  makerStats: PublicKey;
@@ -1070,6 +1093,11 @@ export type ReferrerInfo = {
1070
1093
  referrerStats: PublicKey;
1071
1094
  };
1072
1095
 
1096
+ export enum PlaceAndTakeOrderSuccessCondition {
1097
+ PartialFill = 1,
1098
+ FullFill = 2,
1099
+ }
1100
+
1073
1101
  type ExactType<T> = Pick<T, keyof T>;
1074
1102
 
1075
1103
  export type BaseTxParams = ExactType<{
@@ -1097,6 +1125,7 @@ export interface IWallet {
1097
1125
  signTransaction(tx: Transaction): Promise<Transaction>;
1098
1126
  signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
1099
1127
  publicKey: PublicKey;
1128
+ payer?: Keypair;
1100
1129
  }
1101
1130
  export interface IVersionedWallet {
1102
1131
  signVersionedTransaction(
@@ -1106,6 +1135,7 @@ export interface IVersionedWallet {
1106
1135
  txs: VersionedTransaction[]
1107
1136
  ): Promise<VersionedTransaction[]>;
1108
1137
  publicKey: PublicKey;
1138
+ payer?: Keypair;
1109
1139
  }
1110
1140
 
1111
1141
  export type FeeStructure = {
@@ -0,0 +1,292 @@
1
+ import {
2
+ ClientSubscriptionId,
3
+ Connection,
4
+ Context,
5
+ RpcResponseAndContext,
6
+ SignatureResult,
7
+ SignatureStatus,
8
+ TransactionConfirmationStatus,
9
+ } from '@solana/web3.js';
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
+ };
22
+
23
+ const confirmationStatusValues: Record<TransactionConfirmationStatus, number> =
24
+ {
25
+ processed: 0,
26
+ confirmed: 1,
27
+ finalized: 2,
28
+ };
29
+
30
+ interface TransactionConfirmationRequest {
31
+ txSig: string;
32
+ desiredConfirmationStatus: TransactionConfirmationStatus;
33
+ timeout: number;
34
+ pollInterval: number;
35
+ searchTransactionHistory: boolean;
36
+ startTime: number;
37
+ resolve: (status: SignatureStatus) => void;
38
+ reject: (error: Error) => void;
39
+ }
40
+
41
+ /**
42
+ * 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.
43
+ */
44
+ export class TransactionConfirmationManager {
45
+ private connection: Connection;
46
+ private pendingConfirmations: Map<string, TransactionConfirmationRequest> =
47
+ new Map();
48
+ private intervalId: NodeJS.Timeout | null = null;
49
+
50
+ constructor(connection: Connection) {
51
+ this.connection = connection;
52
+ }
53
+
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(
161
+ txSig: string,
162
+ desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus,
163
+ timeout = 30000,
164
+ pollInterval = 1000,
165
+ searchTransactionHistory = false
166
+ ): Promise<SignatureStatus> {
167
+ // Interval must be > 400ms and a multiple of 100ms
168
+ if (pollInterval < 400 || pollInterval % 100 !== 0) {
169
+ throw new Error(
170
+ 'Transaction confirmation polling interval must be at least 400ms and a multiple of 100ms'
171
+ );
172
+ }
173
+
174
+ return new Promise((resolve, reject) => {
175
+ this.pendingConfirmations.set(txSig, {
176
+ txSig,
177
+ desiredConfirmationStatus,
178
+ timeout,
179
+ pollInterval,
180
+ searchTransactionHistory,
181
+ startTime: Date.now(),
182
+ resolve,
183
+ reject,
184
+ });
185
+
186
+ if (!this.intervalId) {
187
+ this.startConfirmationLoop();
188
+ }
189
+ });
190
+ }
191
+
192
+ private startConfirmationLoop() {
193
+ this.intervalId = setInterval(() => this.checkPendingConfirmations(), 100);
194
+ }
195
+
196
+ private async checkPendingConfirmations() {
197
+ const now = Date.now();
198
+ const transactionsToCheck: TransactionConfirmationRequest[] = [];
199
+
200
+ for (const [txSig, request] of this.pendingConfirmations.entries()) {
201
+ if (now - request.startTime >= request.timeout) {
202
+ request.reject(
203
+ new Error(
204
+ `Transaction confirmation timeout after ${request.timeout}ms`
205
+ )
206
+ );
207
+ this.pendingConfirmations.delete(txSig);
208
+ } else if ((now - request.startTime) % request.pollInterval < 100) {
209
+ transactionsToCheck.push(request);
210
+ }
211
+ }
212
+
213
+ if (transactionsToCheck.length > 0) {
214
+ await this.checkTransactionStatuses(transactionsToCheck);
215
+ }
216
+
217
+ if (this.pendingConfirmations.size === 0 && this.intervalId) {
218
+ clearInterval(this.intervalId);
219
+ this.intervalId = null;
220
+ }
221
+ }
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
+
238
+ private async checkTransactionStatuses(
239
+ requests: TransactionConfirmationRequest[]
240
+ ) {
241
+ const txSigs = requests.map((request) => request.txSig);
242
+ const { value: statuses } = await this.connection.getSignatureStatuses(
243
+ txSigs,
244
+ {
245
+ searchTransactionHistory: requests.some(
246
+ (req) => req.searchTransactionHistory
247
+ ),
248
+ }
249
+ );
250
+
251
+ if (!statuses || statuses.length !== txSigs.length) {
252
+ throw new Error('Failed to get signature statuses');
253
+ }
254
+
255
+ for (let i = 0; i < statuses.length; i++) {
256
+ const status = statuses[i];
257
+ const request = requests[i];
258
+
259
+ if (status === null) {
260
+ continue;
261
+ }
262
+
263
+ if (status.err) {
264
+ this.pendingConfirmations.delete(request.txSig);
265
+ request.reject(
266
+ await getTransactionErrorFromTxSig(request.txSig, this.connection)
267
+ );
268
+ continue;
269
+ }
270
+
271
+ if (
272
+ confirmationStatusValues[status.confirmationStatus] === undefined ||
273
+ confirmationStatusValues[request.desiredConfirmationStatus] ===
274
+ undefined
275
+ ) {
276
+ throw new Error(
277
+ `Invalid confirmation status when awaiting confirmation: ${status.confirmationStatus}`
278
+ );
279
+ }
280
+
281
+ if (
282
+ this.checkStatusMatchesDesiredConfirmationStatus(
283
+ status,
284
+ request.desiredConfirmationStatus
285
+ )
286
+ ) {
287
+ request.resolve(status);
288
+ this.pendingConfirmations.delete(request.txSig);
289
+ }
290
+ }
291
+ }
292
+ }
@@ -6,6 +6,7 @@ import {
6
6
  MainnetPerpMarkets,
7
7
  BulkAccountLoader,
8
8
  getVariant,
9
+ isOneOfVariant,
9
10
  } from '../../src';
10
11
  import { Connection, Keypair } from '@solana/web3.js';
11
12
  import { Wallet } from '@coral-xyz/anchor';
@@ -128,6 +129,12 @@ describe('Verify Constants', function () {
128
129
  market.marketIndex
129
130
  } oracle ${market.oracle.toBase58()}`
130
131
  );
132
+
133
+ if (isOneOfVariant(market.oracleSource, ['pythPull', 'pyth1KPull', 'pyth1MPull', 'pythStableCoinPull'])) {
134
+ if (!correspondingConfigMarket.pythFeedId) {
135
+ assert(false, `spot market ${market.marketIndex} missing feed id`);
136
+ }
137
+ }
131
138
  }
132
139
 
133
140
  const perpMarkets = mainnetDriftClient.getPerpMarketAccounts();
@@ -177,6 +184,12 @@ describe('Verify Constants', function () {
177
184
  market.marketIndex
178
185
  } oracle ${market.amm.oracle.toBase58()}`
179
186
  );
187
+
188
+ if (isOneOfVariant(market.amm.oracleSource, ['pythPull', 'pyth1KPull', 'pyth1MPull', 'pythStableCoinPull'])) {
189
+ if (!correspondingConfigMarket.pythFeedId) {
190
+ assert(false, `perp market ${market.marketIndex} missing feed id`);
191
+ }
192
+ }
180
193
  }
181
194
  });
182
195