@solana/web3.js 1.41.8 → 1.42.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana/web3.js",
3
- "version": "1.41.8",
3
+ "version": "1.42.0",
4
4
  "description": "Solana Javascript API",
5
5
  "keywords": [
6
6
  "api",
@@ -129,7 +129,7 @@
129
129
  "sinon": "^13.0.2",
130
130
  "sinon-chai": "^3.7.0",
131
131
  "start-server-and-test": "^1.12.0",
132
- "ts-mocha": "^9.0.2",
132
+ "ts-mocha": "^10.0.0",
133
133
  "ts-node": "^10.0.0",
134
134
  "tslib": "^2.1.0",
135
135
  "typedoc": "^0.22.2",
package/src/connection.ts CHANGED
@@ -32,12 +32,15 @@ import {NonceAccount} from './nonce-account';
32
32
  import {PublicKey} from './publickey';
33
33
  import {Signer} from './keypair';
34
34
  import {MS_PER_SLOT} from './timing';
35
- import {Transaction} from './transaction';
35
+ import {Transaction, TransactionStatus} from './transaction';
36
36
  import {Message} from './message';
37
37
  import assert from './util/assert';
38
38
  import {sleep} from './util/sleep';
39
- import {promiseTimeout} from './util/promise-timeout';
40
39
  import {toBuffer} from './util/to-buffer';
40
+ import {
41
+ TransactionExpiredBlockheightExceededError,
42
+ TransactionExpiredTimeoutError,
43
+ } from './util/tx-expiry-custom-errors';
41
44
  import {makeWebsocketUrl} from './util/url';
42
45
  import type {Blockhash} from './blockhash';
43
46
  import type {FeeCalculator} from './fee-calculator';
@@ -281,6 +284,19 @@ export type RpcResponseAndContext<T> = {
281
284
  value: T;
282
285
  };
283
286
 
287
+ export type BlockhashWithExpiryBlockHeight = Readonly<{
288
+ blockhash: Blockhash;
289
+ lastValidBlockHeight: number;
290
+ }>;
291
+
292
+ /**
293
+ * A strategy for confirming transactions that uses the last valid
294
+ * block height for a given blockhash to check for transaction expiration.
295
+ */
296
+ export type BlockheightBasedTransactionConfimationStrategy = {
297
+ signature: TransactionSignature;
298
+ } & BlockhashWithExpiryBlockHeight;
299
+
284
300
  /**
285
301
  * @internal
286
302
  */
@@ -2205,12 +2221,12 @@ export class Connection {
2205
2221
  /** @internal */ _disableBlockhashCaching: boolean = false;
2206
2222
  /** @internal */ _pollingBlockhash: boolean = false;
2207
2223
  /** @internal */ _blockhashInfo: {
2208
- recentBlockhash: Blockhash | null;
2224
+ latestBlockhash: BlockhashWithExpiryBlockHeight | null;
2209
2225
  lastFetch: number;
2210
2226
  simulatedSignatures: Array<string>;
2211
2227
  transactionSignatures: Array<string>;
2212
2228
  } = {
2213
- recentBlockhash: null,
2229
+ latestBlockhash: null,
2214
2230
  lastFetch: 0,
2215
2231
  transactionSignatures: [],
2216
2232
  simulatedSignatures: [],
@@ -2825,38 +2841,64 @@ export class Connection {
2825
2841
  return res.result;
2826
2842
  }
2827
2843
 
2828
- /**
2829
- * Confirm the transaction identified by the specified signature.
2830
- */
2844
+ confirmTransaction(
2845
+ strategy: BlockheightBasedTransactionConfimationStrategy,
2846
+ commitment?: Commitment,
2847
+ ): Promise<RpcResponseAndContext<SignatureResult>>;
2848
+
2849
+ /** @deprecated Instead, call `confirmTransaction` using a `TransactionConfirmationConfig` */
2850
+ // eslint-disable-next-line no-dupe-class-members
2851
+ confirmTransaction(
2852
+ strategy: TransactionSignature,
2853
+ commitment?: Commitment,
2854
+ ): Promise<RpcResponseAndContext<SignatureResult>>;
2855
+
2856
+ // eslint-disable-next-line no-dupe-class-members
2831
2857
  async confirmTransaction(
2832
- signature: TransactionSignature,
2858
+ strategy:
2859
+ | BlockheightBasedTransactionConfimationStrategy
2860
+ | TransactionSignature,
2833
2861
  commitment?: Commitment,
2834
2862
  ): Promise<RpcResponseAndContext<SignatureResult>> {
2863
+ let rawSignature: string;
2864
+
2865
+ if (typeof strategy == 'string') {
2866
+ rawSignature = strategy;
2867
+ } else {
2868
+ const config = strategy as BlockheightBasedTransactionConfimationStrategy;
2869
+ rawSignature = config.signature;
2870
+ }
2871
+
2835
2872
  let decodedSignature;
2873
+
2836
2874
  try {
2837
- decodedSignature = bs58.decode(signature);
2875
+ decodedSignature = bs58.decode(rawSignature);
2838
2876
  } catch (err) {
2839
- throw new Error('signature must be base58 encoded: ' + signature);
2877
+ throw new Error('signature must be base58 encoded: ' + rawSignature);
2840
2878
  }
2841
2879
 
2842
2880
  assert(decodedSignature.length === 64, 'signature has invalid length');
2843
2881
 
2844
- const start = Date.now();
2845
2882
  const subscriptionCommitment = commitment || this.commitment;
2846
-
2883
+ let timeoutId;
2847
2884
  let subscriptionId;
2848
- let response: RpcResponseAndContext<SignatureResult> | null = null;
2849
- const confirmPromise = new Promise((resolve, reject) => {
2885
+ let done = false;
2886
+
2887
+ const confirmationPromise = new Promise<{
2888
+ __type: TransactionStatus.PROCESSED;
2889
+ response: RpcResponseAndContext<SignatureResult>;
2890
+ }>((resolve, reject) => {
2850
2891
  try {
2851
2892
  subscriptionId = this.onSignature(
2852
- signature,
2893
+ rawSignature,
2853
2894
  (result: SignatureResult, context: Context) => {
2854
2895
  subscriptionId = undefined;
2855
- response = {
2896
+ const response = {
2856
2897
  context,
2857
2898
  value: result,
2858
2899
  };
2859
- resolve(null);
2900
+ done = true;
2901
+ resolve({__type: TransactionStatus.PROCESSED, response});
2860
2902
  },
2861
2903
  subscriptionCommitment,
2862
2904
  );
@@ -2865,40 +2907,78 @@ export class Connection {
2865
2907
  }
2866
2908
  });
2867
2909
 
2868
- let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
2869
- switch (subscriptionCommitment) {
2870
- case 'processed':
2871
- case 'recent':
2872
- case 'single':
2873
- case 'confirmed':
2874
- case 'singleGossip': {
2875
- timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
2876
- break;
2910
+ const checkBlockHeight = async () => {
2911
+ try {
2912
+ const blockHeight = await this.getBlockHeight(commitment);
2913
+ return blockHeight;
2914
+ } catch (_e) {
2915
+ return -1;
2877
2916
  }
2878
- // exhaust enums to ensure full coverage
2879
- case 'finalized':
2880
- case 'max':
2881
- case 'root':
2882
- }
2917
+ };
2918
+
2919
+ const expiryPromise = new Promise<
2920
+ | {__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED}
2921
+ | {__type: TransactionStatus.TIMED_OUT; timeoutMs: number}
2922
+ >(resolve => {
2923
+ if (typeof strategy === 'string') {
2924
+ let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
2925
+ switch (subscriptionCommitment) {
2926
+ case 'processed':
2927
+ case 'recent':
2928
+ case 'single':
2929
+ case 'confirmed':
2930
+ case 'singleGossip': {
2931
+ timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
2932
+ break;
2933
+ }
2934
+ // exhaust enums to ensure full coverage
2935
+ case 'finalized':
2936
+ case 'max':
2937
+ case 'root':
2938
+ }
2939
+
2940
+ timeoutId = setTimeout(
2941
+ () => resolve({__type: TransactionStatus.TIMED_OUT, timeoutMs}),
2942
+ timeoutMs,
2943
+ );
2944
+ } else {
2945
+ let config = strategy as BlockheightBasedTransactionConfimationStrategy;
2946
+ (async () => {
2947
+ let currentBlockHeight = await checkBlockHeight();
2948
+ if (done) return;
2949
+ while (currentBlockHeight <= config.lastValidBlockHeight) {
2950
+ await sleep(1000);
2951
+ if (done) return;
2952
+ currentBlockHeight = await checkBlockHeight();
2953
+ if (done) return;
2954
+ }
2955
+ resolve({__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED});
2956
+ })();
2957
+ }
2958
+ });
2883
2959
 
2960
+ let result: RpcResponseAndContext<SignatureResult>;
2884
2961
  try {
2885
- await promiseTimeout(confirmPromise, timeoutMs);
2962
+ const outcome = await Promise.race([confirmationPromise, expiryPromise]);
2963
+ switch (outcome.__type) {
2964
+ case TransactionStatus.BLOCKHEIGHT_EXCEEDED:
2965
+ throw new TransactionExpiredBlockheightExceededError(rawSignature);
2966
+ case TransactionStatus.PROCESSED:
2967
+ result = outcome.response;
2968
+ break;
2969
+ case TransactionStatus.TIMED_OUT:
2970
+ throw new TransactionExpiredTimeoutError(
2971
+ rawSignature,
2972
+ outcome.timeoutMs / 1000,
2973
+ );
2974
+ }
2886
2975
  } finally {
2976
+ clearTimeout(timeoutId);
2887
2977
  if (subscriptionId) {
2888
2978
  this.removeSignatureListener(subscriptionId);
2889
2979
  }
2890
2980
  }
2891
-
2892
- if (response === null) {
2893
- const duration = (Date.now() - start) / 1000;
2894
- throw new Error(
2895
- `Transaction was not confirmed in ${duration.toFixed(
2896
- 2,
2897
- )} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`,
2898
- );
2899
- }
2900
-
2901
- return response;
2981
+ return result;
2902
2982
  }
2903
2983
 
2904
2984
  /**
@@ -3245,11 +3325,11 @@ export class Connection {
3245
3325
 
3246
3326
  /**
3247
3327
  * Fetch the latest blockhash from the cluster
3248
- * @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
3328
+ * @return {Promise<BlockhashWithExpiryBlockHeight>}
3249
3329
  */
3250
3330
  async getLatestBlockhash(
3251
3331
  commitment?: Commitment,
3252
- ): Promise<{blockhash: Blockhash; lastValidBlockHeight: number}> {
3332
+ ): Promise<BlockhashWithExpiryBlockHeight> {
3253
3333
  try {
3254
3334
  const res = await this.getLatestBlockhashAndContext(commitment);
3255
3335
  return res.value;
@@ -3260,13 +3340,11 @@ export class Connection {
3260
3340
 
3261
3341
  /**
3262
3342
  * Fetch the latest blockhash from the cluster
3263
- * @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
3343
+ * @return {Promise<BlockhashWithExpiryBlockHeight>}
3264
3344
  */
3265
3345
  async getLatestBlockhashAndContext(
3266
3346
  commitment?: Commitment,
3267
- ): Promise<
3268
- RpcResponseAndContext<{blockhash: Blockhash; lastValidBlockHeight: number}>
3269
- > {
3347
+ ): Promise<RpcResponseAndContext<BlockhashWithExpiryBlockHeight>> {
3270
3348
  const args = this._buildArgs([], commitment);
3271
3349
  const unsafeRes = await this._rpcRequest('getLatestBlockhash', args);
3272
3350
  const res = create(unsafeRes, GetLatestBlockhashRpcResult);
@@ -3912,7 +3990,9 @@ export class Connection {
3912
3990
  /**
3913
3991
  * @internal
3914
3992
  */
3915
- async _recentBlockhash(disableCache: boolean): Promise<Blockhash> {
3993
+ async _blockhashWithExpiryBlockHeight(
3994
+ disableCache: boolean,
3995
+ ): Promise<BlockhashWithExpiryBlockHeight> {
3916
3996
  if (!disableCache) {
3917
3997
  // Wait for polling to finish
3918
3998
  while (this._pollingBlockhash) {
@@ -3920,8 +4000,8 @@ export class Connection {
3920
4000
  }
3921
4001
  const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch;
3922
4002
  const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS;
3923
- if (this._blockhashInfo.recentBlockhash !== null && !expired) {
3924
- return this._blockhashInfo.recentBlockhash;
4003
+ if (this._blockhashInfo.latestBlockhash !== null && !expired) {
4004
+ return this._blockhashInfo.latestBlockhash;
3925
4005
  }
3926
4006
  }
3927
4007
 
@@ -3931,21 +4011,25 @@ export class Connection {
3931
4011
  /**
3932
4012
  * @internal
3933
4013
  */
3934
- async _pollNewBlockhash(): Promise<Blockhash> {
4014
+ async _pollNewBlockhash(): Promise<BlockhashWithExpiryBlockHeight> {
3935
4015
  this._pollingBlockhash = true;
3936
4016
  try {
3937
4017
  const startTime = Date.now();
4018
+ const cachedLatestBlockhash = this._blockhashInfo.latestBlockhash;
4019
+ const cachedBlockhash = cachedLatestBlockhash
4020
+ ? cachedLatestBlockhash.blockhash
4021
+ : null;
3938
4022
  for (let i = 0; i < 50; i++) {
3939
- const {blockhash} = await this.getRecentBlockhash('finalized');
4023
+ const latestBlockhash = await this.getLatestBlockhash('finalized');
3940
4024
 
3941
- if (this._blockhashInfo.recentBlockhash != blockhash) {
4025
+ if (cachedBlockhash !== latestBlockhash.blockhash) {
3942
4026
  this._blockhashInfo = {
3943
- recentBlockhash: blockhash,
4027
+ latestBlockhash,
3944
4028
  lastFetch: Date.now(),
3945
4029
  transactionSignatures: [],
3946
4030
  simulatedSignatures: [],
3947
4031
  };
3948
- return blockhash;
4032
+ return latestBlockhash;
3949
4033
  }
3950
4034
 
3951
4035
  // Sleep for approximately half a slot
@@ -3971,13 +4055,11 @@ export class Connection {
3971
4055
  let transaction;
3972
4056
  if (transactionOrMessage instanceof Transaction) {
3973
4057
  let originalTx: Transaction = transactionOrMessage;
3974
- transaction = new Transaction({
3975
- recentBlockhash: originalTx.recentBlockhash,
3976
- nonceInfo: originalTx.nonceInfo,
3977
- feePayer: originalTx.feePayer,
3978
- signatures: [...originalTx.signatures],
3979
- });
4058
+ transaction = new Transaction();
4059
+ transaction.feePayer = originalTx.feePayer;
3980
4060
  transaction.instructions = transactionOrMessage.instructions;
4061
+ transaction.nonceInfo = originalTx.nonceInfo;
4062
+ transaction.signatures = originalTx.signatures;
3981
4063
  } else {
3982
4064
  transaction = Transaction.populate(transactionOrMessage);
3983
4065
  // HACK: this function relies on mutating the populated transaction
@@ -3989,7 +4071,11 @@ export class Connection {
3989
4071
  } else {
3990
4072
  let disableCache = this._disableBlockhashCaching;
3991
4073
  for (;;) {
3992
- transaction.recentBlockhash = await this._recentBlockhash(disableCache);
4074
+ const latestBlockhash = await this._blockhashWithExpiryBlockHeight(
4075
+ disableCache,
4076
+ );
4077
+ transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
4078
+ transaction.recentBlockhash = latestBlockhash.blockhash;
3993
4079
 
3994
4080
  if (!signers) break;
3995
4081
 
@@ -4077,7 +4163,11 @@ export class Connection {
4077
4163
  } else {
4078
4164
  let disableCache = this._disableBlockhashCaching;
4079
4165
  for (;;) {
4080
- transaction.recentBlockhash = await this._recentBlockhash(disableCache);
4166
+ const latestBlockhash = await this._blockhashWithExpiryBlockHeight(
4167
+ disableCache,
4168
+ );
4169
+ transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
4170
+ transaction.recentBlockhash = latestBlockhash.blockhash;
4081
4171
  transaction.sign(...signers);
4082
4172
  if (!transaction.signature) {
4083
4173
  throw new Error('!signature'); // should never happen
@@ -4174,6 +4264,7 @@ export class Connection {
4174
4264
  * @internal
4175
4265
  */
4176
4266
  _wsOnError(err: Error) {
4267
+ this._rpcWebSocketConnected = false;
4177
4268
  console.error('ws error:', err.message);
4178
4269
  }
4179
4270
 
@@ -4181,6 +4272,7 @@ export class Connection {
4181
4272
  * @internal
4182
4273
  */
4183
4274
  _wsOnClose(code: number) {
4275
+ this._rpcWebSocketConnected = false;
4184
4276
  this._rpcWebSocketGeneration++;
4185
4277
  if (this._rpcWebSocketHeartbeat) {
4186
4278
  clearInterval(this._rpcWebSocketHeartbeat);
@@ -21,6 +21,12 @@ import type {CompiledInstruction} from './message';
21
21
  */
22
22
  export type TransactionSignature = string;
23
23
 
24
+ export const enum TransactionStatus {
25
+ BLOCKHEIGHT_EXCEEDED,
26
+ PROCESSED,
27
+ TIMED_OUT,
28
+ }
29
+
24
30
  /**
25
31
  * Default (empty) signature
26
32
  */
@@ -124,17 +130,30 @@ export type SignaturePubkeyPair = {
124
130
 
125
131
  /**
126
132
  * List of Transaction object fields that may be initialized at construction
127
- *
128
133
  */
129
- export type TransactionCtorFields = {
130
- /** A recent blockhash */
131
- recentBlockhash?: Blockhash | null;
134
+ export type TransactionCtorFields_DEPRECATED = {
132
135
  /** Optional nonce information used for offline nonce'd transactions */
133
136
  nonceInfo?: NonceInformation | null;
134
137
  /** The transaction fee payer */
135
138
  feePayer?: PublicKey | null;
136
139
  /** One or more signatures */
137
140
  signatures?: Array<SignaturePubkeyPair>;
141
+ /** A recent blockhash */
142
+ recentBlockhash?: Blockhash;
143
+ };
144
+
145
+ /**
146
+ * List of Transaction object fields that may be initialized at construction
147
+ */
148
+ export type TransactionBlockhashCtor = {
149
+ /** The transaction fee payer */
150
+ feePayer?: PublicKey | null;
151
+ /** One or more signatures */
152
+ signatures?: Array<SignaturePubkeyPair>;
153
+ /** A recent blockhash */
154
+ blockhash: Blockhash;
155
+ /** the last block chain can advance to before tx is declared expired */
156
+ lastValidBlockHeight: number;
138
157
  };
139
158
 
140
159
  /**
@@ -196,6 +215,11 @@ export class Transaction {
196
215
  */
197
216
  recentBlockhash?: Blockhash;
198
217
 
218
+ /**
219
+ * the last block chain can advance to before tx is declared expired
220
+ * */
221
+ lastValidBlockHeight?: number;
222
+
199
223
  /**
200
224
  * Optional Nonce information. If populated, transaction will use a durable
201
225
  * Nonce hash instead of a recentBlockhash. Must be populated by the caller
@@ -212,11 +236,35 @@ export class Transaction {
212
236
  */
213
237
  _json?: TransactionJSON;
214
238
 
239
+ // Construct a transaction with a blockhash and lastValidBlockHeight
240
+ constructor(opts?: TransactionBlockhashCtor);
241
+
242
+ /**
243
+ * @deprecated `TransactionCtorFields` has been deprecated and will be removed in a future version.
244
+ * Please supply a `TransactionBlockhashCtor` instead.
245
+ */
246
+ constructor(opts?: TransactionCtorFields_DEPRECATED);
247
+
215
248
  /**
216
249
  * Construct an empty Transaction
217
250
  */
218
- constructor(opts?: TransactionCtorFields) {
219
- opts && Object.assign(this, opts);
251
+ constructor(
252
+ opts?: TransactionBlockhashCtor | TransactionCtorFields_DEPRECATED,
253
+ ) {
254
+ if (!opts) {
255
+ return;
256
+ } else if (
257
+ Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')
258
+ ) {
259
+ const newOpts = opts as TransactionBlockhashCtor;
260
+ Object.assign(this, newOpts);
261
+ this.recentBlockhash = newOpts.blockhash;
262
+ this.lastValidBlockHeight = newOpts.lastValidBlockHeight;
263
+ } else {
264
+ const oldOpts = opts as TransactionCtorFields_DEPRECATED;
265
+ Object.assign(this, oldOpts);
266
+ this.recentBlockhash = oldOpts.recentBlockhash;
267
+ }
220
268
  }
221
269
 
222
270
  /**
@@ -1,6 +1,9 @@
1
1
  import type {Buffer} from 'buffer';
2
2
 
3
- import {Connection} from '../connection';
3
+ import {
4
+ BlockheightBasedTransactionConfimationStrategy,
5
+ Connection,
6
+ } from '../connection';
4
7
  import type {TransactionSignature} from '../transaction';
5
8
  import type {ConfirmOptions} from '../connection';
6
9
 
@@ -11,14 +14,57 @@ import type {ConfirmOptions} from '../connection';
11
14
  *
12
15
  * @param {Connection} connection
13
16
  * @param {Buffer} rawTransaction
17
+ * @param {BlockheightBasedTransactionConfimationStrategy} confirmationStrategy
14
18
  * @param {ConfirmOptions} [options]
15
19
  * @returns {Promise<TransactionSignature>}
16
20
  */
17
21
  export async function sendAndConfirmRawTransaction(
18
22
  connection: Connection,
19
23
  rawTransaction: Buffer,
24
+ confirmationStrategy: BlockheightBasedTransactionConfimationStrategy,
20
25
  options?: ConfirmOptions,
26
+ ): Promise<TransactionSignature>;
27
+
28
+ /**
29
+ * @deprecated Calling `sendAndConfirmRawTransaction()` without a `confirmationStrategy`
30
+ * is no longer supported and will be removed in a future version.
31
+ */
32
+ // eslint-disable-next-line no-redeclare
33
+ export async function sendAndConfirmRawTransaction(
34
+ connection: Connection,
35
+ rawTransaction: Buffer,
36
+ options?: ConfirmOptions,
37
+ ): Promise<TransactionSignature>;
38
+
39
+ // eslint-disable-next-line no-redeclare
40
+ export async function sendAndConfirmRawTransaction(
41
+ connection: Connection,
42
+ rawTransaction: Buffer,
43
+ confirmationStrategyOrConfirmOptions:
44
+ | BlockheightBasedTransactionConfimationStrategy
45
+ | ConfirmOptions
46
+ | undefined,
47
+ maybeConfirmOptions?: ConfirmOptions,
21
48
  ): Promise<TransactionSignature> {
49
+ let confirmationStrategy:
50
+ | BlockheightBasedTransactionConfimationStrategy
51
+ | undefined;
52
+ let options: ConfirmOptions | undefined;
53
+ if (
54
+ confirmationStrategyOrConfirmOptions &&
55
+ Object.prototype.hasOwnProperty.call(
56
+ confirmationStrategyOrConfirmOptions,
57
+ 'lastValidBlockHeight',
58
+ )
59
+ ) {
60
+ confirmationStrategy =
61
+ confirmationStrategyOrConfirmOptions as BlockheightBasedTransactionConfimationStrategy;
62
+ options = maybeConfirmOptions;
63
+ } else {
64
+ options = confirmationStrategyOrConfirmOptions as
65
+ | ConfirmOptions
66
+ | undefined;
67
+ }
22
68
  const sendOptions = options && {
23
69
  skipPreflight: options.skipPreflight,
24
70
  preflightCommitment: options.preflightCommitment || options.commitment,
@@ -29,12 +75,11 @@ export async function sendAndConfirmRawTransaction(
29
75
  sendOptions,
30
76
  );
31
77
 
32
- const status = (
33
- await connection.confirmTransaction(
34
- signature,
35
- options && options.commitment,
36
- )
37
- ).value;
78
+ const commitment = options && options.commitment;
79
+ const confirmationPromise = confirmationStrategy
80
+ ? connection.confirmTransaction(confirmationStrategy, commitment)
81
+ : connection.confirmTransaction(signature, commitment);
82
+ const status = (await confirmationPromise).value;
38
83
 
39
84
  if (status.err) {
40
85
  throw new Error(
@@ -33,12 +33,25 @@ export async function sendAndConfirmTransaction(
33
33
  sendOptions,
34
34
  );
35
35
 
36
- const status = (
37
- await connection.confirmTransaction(
38
- signature,
39
- options && options.commitment,
40
- )
41
- ).value;
36
+ const status =
37
+ transaction.recentBlockhash != null &&
38
+ transaction.lastValidBlockHeight != null
39
+ ? (
40
+ await connection.confirmTransaction(
41
+ {
42
+ signature: signature,
43
+ blockhash: transaction.recentBlockhash,
44
+ lastValidBlockHeight: transaction.lastValidBlockHeight,
45
+ },
46
+ options && options.commitment,
47
+ )
48
+ ).value
49
+ : (
50
+ await connection.confirmTransaction(
51
+ signature,
52
+ options && options.commitment,
53
+ )
54
+ ).value;
42
55
 
43
56
  if (status.err) {
44
57
  throw new Error(
@@ -0,0 +1,35 @@
1
+ export class TransactionExpiredBlockheightExceededError extends Error {
2
+ signature: string;
3
+
4
+ constructor(signature: string) {
5
+ super(`Signature ${signature} has expired: block height exceeded.`);
6
+ this.signature = signature;
7
+ }
8
+ }
9
+
10
+ Object.defineProperty(
11
+ TransactionExpiredBlockheightExceededError.prototype,
12
+ 'name',
13
+ {
14
+ value: 'TransactionExpiredBlockheightExceededError',
15
+ },
16
+ );
17
+
18
+ export class TransactionExpiredTimeoutError extends Error {
19
+ signature: string;
20
+
21
+ constructor(signature: string, timeoutSeconds: number) {
22
+ super(
23
+ `Transaction was not confirmed in ${timeoutSeconds.toFixed(
24
+ 2,
25
+ )} seconds. It is ` +
26
+ 'unknown if it succeeded or failed. Check signature ' +
27
+ `${signature} using the Solana Explorer or CLI tools.`,
28
+ );
29
+ this.signature = signature;
30
+ }
31
+ }
32
+
33
+ Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', {
34
+ value: 'TransactionExpiredTimeoutError',
35
+ });