@solana/web3.js 1.66.5 → 1.67.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.66.5",
3
+ "version": "1.67.0",
4
4
  "description": "Solana Javascript API",
5
5
  "keywords": [
6
6
  "api",
package/src/connection.ts CHANGED
@@ -28,7 +28,7 @@ import {AgentManager} from './agent-manager';
28
28
  import {EpochSchedule} from './epoch-schedule';
29
29
  import {SendTransactionError, SolanaJSONRPCError} from './errors';
30
30
  import fetchImpl, {Response} from './fetch-impl';
31
- import {NonceAccount} from './nonce-account';
31
+ import {DurableNonce, NonceAccount} from './nonce-account';
32
32
  import {PublicKey} from './publickey';
33
33
  import {Signer} from './keypair';
34
34
  import {MS_PER_SLOT} from './timing';
@@ -45,6 +45,7 @@ import {sleep} from './utils/sleep';
45
45
  import {toBuffer} from './utils/to-buffer';
46
46
  import {
47
47
  TransactionExpiredBlockheightExceededError,
48
+ TransactionExpiredNonceInvalidError,
48
49
  TransactionExpiredTimeoutError,
49
50
  } from './transaction/expiry-custom-errors';
50
51
  import {makeWebsocketUrl} from './utils/makeWebsocketUrl';
@@ -338,6 +339,28 @@ function extractCommitmentFromConfig<TConfig>(
338
339
  return {commitment, config};
339
340
  }
340
341
 
342
+ /**
343
+ * A strategy for confirming durable nonce transactions.
344
+ */
345
+ export type DurableNonceTransactionConfirmationStrategy = {
346
+ /**
347
+ * The lowest slot at which to fetch the nonce value from the
348
+ * nonce account. This should be no lower than the slot at
349
+ * which the last-known value of the nonce was fetched.
350
+ */
351
+ minContextSlot: number;
352
+ /**
353
+ * The account where the current value of the nonce is stored.
354
+ */
355
+ nonceAccountPubkey: PublicKey;
356
+ /**
357
+ * The nonce value that was used to sign the transaction
358
+ * for which confirmation is being sought.
359
+ */
360
+ nonceValue: DurableNonce;
361
+ signature: TransactionSignature;
362
+ };
363
+
341
364
  /**
342
365
  * @internal
343
366
  */
@@ -2438,6 +2461,26 @@ export type GetTransactionCountConfig = {
2438
2461
  minContextSlot?: number;
2439
2462
  };
2440
2463
 
2464
+ /**
2465
+ * Configuration object for `getNonce`
2466
+ */
2467
+ export type GetNonceConfig = {
2468
+ /** Optional commitment level */
2469
+ commitment?: Commitment;
2470
+ /** The minimum slot that the request can be evaluated at */
2471
+ minContextSlot?: number;
2472
+ };
2473
+
2474
+ /**
2475
+ * Configuration object for `getNonceAndContext`
2476
+ */
2477
+ export type GetNonceAndContextConfig = {
2478
+ /** Optional commitment level */
2479
+ commitment?: Commitment;
2480
+ /** The minimum slot that the request can be evaluated at */
2481
+ minContextSlot?: number;
2482
+ };
2483
+
2441
2484
  /**
2442
2485
  * Information describing an account
2443
2486
  */
@@ -3348,7 +3391,9 @@ export class Connection {
3348
3391
  }
3349
3392
 
3350
3393
  confirmTransaction(
3351
- strategy: BlockheightBasedTransactionConfirmationStrategy,
3394
+ strategy:
3395
+ | BlockheightBasedTransactionConfirmationStrategy
3396
+ | DurableNonceTransactionConfirmationStrategy,
3352
3397
  commitment?: Commitment,
3353
3398
  ): Promise<RpcResponseAndContext<SignatureResult>>;
3354
3399
 
@@ -3363,6 +3408,7 @@ export class Connection {
3363
3408
  async confirmTransaction(
3364
3409
  strategy:
3365
3410
  | BlockheightBasedTransactionConfirmationStrategy
3411
+ | DurableNonceTransactionConfirmationStrategy
3366
3412
  | TransactionSignature,
3367
3413
  commitment?: Commitment,
3368
3414
  ): Promise<RpcResponseAndContext<SignatureResult>> {
@@ -3371,8 +3417,9 @@ export class Connection {
3371
3417
  if (typeof strategy == 'string') {
3372
3418
  rawSignature = strategy;
3373
3419
  } else {
3374
- const config =
3375
- strategy as BlockheightBasedTransactionConfirmationStrategy;
3420
+ const config = strategy as
3421
+ | BlockheightBasedTransactionConfirmationStrategy
3422
+ | DurableNonceTransactionConfirmationStrategy;
3376
3423
  rawSignature = config.signature;
3377
3424
  }
3378
3425
 
@@ -3386,31 +3433,58 @@ export class Connection {
3386
3433
 
3387
3434
  assert(decodedSignature.length === 64, 'signature has invalid length');
3388
3435
 
3389
- const confirmationCommitment = commitment || this.commitment;
3390
- let timeoutId;
3436
+ if (typeof strategy === 'string') {
3437
+ return await this.confirmTransactionUsingLegacyTimeoutStrategy({
3438
+ commitment: commitment || this.commitment,
3439
+ signature: rawSignature,
3440
+ });
3441
+ } else if ('lastValidBlockHeight' in strategy) {
3442
+ return await this.confirmTransactionUsingBlockHeightExceedanceStrategy({
3443
+ commitment: commitment || this.commitment,
3444
+ strategy,
3445
+ });
3446
+ } else {
3447
+ return await this.confirmTransactionUsingDurableNonceStrategy({
3448
+ commitment: commitment || this.commitment,
3449
+ strategy,
3450
+ });
3451
+ }
3452
+ }
3453
+
3454
+ private getTransactionConfirmationPromise({
3455
+ commitment,
3456
+ signature,
3457
+ }: {
3458
+ commitment?: Commitment;
3459
+ signature: string;
3460
+ }): {
3461
+ abortConfirmation(): void;
3462
+ confirmationPromise: Promise<{
3463
+ __type: TransactionStatus.PROCESSED;
3464
+ response: RpcResponseAndContext<SignatureResult>;
3465
+ }>;
3466
+ } {
3391
3467
  let signatureSubscriptionId: number | undefined;
3392
3468
  let disposeSignatureSubscriptionStateChangeObserver:
3393
3469
  | SubscriptionStateChangeDisposeFn
3394
3470
  | undefined;
3395
3471
  let done = false;
3396
-
3397
3472
  const confirmationPromise = new Promise<{
3398
3473
  __type: TransactionStatus.PROCESSED;
3399
3474
  response: RpcResponseAndContext<SignatureResult>;
3400
3475
  }>((resolve, reject) => {
3401
3476
  try {
3402
3477
  signatureSubscriptionId = this.onSignature(
3403
- rawSignature,
3478
+ signature,
3404
3479
  (result: SignatureResult, context: Context) => {
3405
3480
  signatureSubscriptionId = undefined;
3406
3481
  const response = {
3407
3482
  context,
3408
3483
  value: result,
3409
3484
  };
3410
- done = true;
3411
3485
  resolve({__type: TransactionStatus.PROCESSED, response});
3412
3486
  },
3413
- confirmationCommitment,
3487
+ commitment,
3414
3488
  );
3415
3489
  const subscriptionSetupPromise = new Promise<void>(
3416
3490
  resolveSubscriptionSetup => {
@@ -3432,7 +3506,7 @@ export class Connection {
3432
3506
  (async () => {
3433
3507
  await subscriptionSetupPromise;
3434
3508
  if (done) return;
3435
- const response = await this.getSignatureStatus(rawSignature);
3509
+ const response = await this.getSignatureStatus(signature);
3436
3510
  if (done) return;
3437
3511
  if (response == null) {
3438
3512
  return;
@@ -3444,7 +3518,7 @@ export class Connection {
3444
3518
  if (value?.err) {
3445
3519
  reject(value.err);
3446
3520
  } else {
3447
- switch (confirmationCommitment) {
3521
+ switch (commitment) {
3448
3522
  case 'confirmed':
3449
3523
  case 'single':
3450
3524
  case 'singleGossip': {
@@ -3482,80 +3556,250 @@ export class Connection {
3482
3556
  reject(err);
3483
3557
  }
3484
3558
  });
3559
+ const abortConfirmation = () => {
3560
+ if (disposeSignatureSubscriptionStateChangeObserver) {
3561
+ disposeSignatureSubscriptionStateChangeObserver();
3562
+ disposeSignatureSubscriptionStateChangeObserver = undefined;
3563
+ }
3564
+ if (signatureSubscriptionId) {
3565
+ this.removeSignatureListener(signatureSubscriptionId);
3566
+ signatureSubscriptionId = undefined;
3567
+ }
3568
+ };
3569
+ return {abortConfirmation, confirmationPromise};
3570
+ }
3485
3571
 
3486
- const expiryPromise = new Promise<
3487
- | {__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED}
3488
- | {__type: TransactionStatus.TIMED_OUT; timeoutMs: number}
3489
- >(resolve => {
3490
- if (typeof strategy === 'string') {
3491
- let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
3492
- switch (confirmationCommitment) {
3493
- case 'processed':
3494
- case 'recent':
3495
- case 'single':
3496
- case 'confirmed':
3497
- case 'singleGossip': {
3498
- timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
3499
- break;
3500
- }
3501
- // exhaust enums to ensure full coverage
3502
- case 'finalized':
3503
- case 'max':
3504
- case 'root':
3572
+ private async confirmTransactionUsingBlockHeightExceedanceStrategy({
3573
+ commitment,
3574
+ strategy: {lastValidBlockHeight, signature},
3575
+ }: {
3576
+ commitment?: Commitment;
3577
+ strategy: BlockheightBasedTransactionConfirmationStrategy;
3578
+ }) {
3579
+ let done: boolean = false;
3580
+ const expiryPromise = new Promise<{
3581
+ __type: TransactionStatus.BLOCKHEIGHT_EXCEEDED;
3582
+ }>(resolve => {
3583
+ const checkBlockHeight = async () => {
3584
+ try {
3585
+ const blockHeight = await this.getBlockHeight(commitment);
3586
+ return blockHeight;
3587
+ } catch (_e) {
3588
+ return -1;
3505
3589
  }
3506
-
3507
- timeoutId = setTimeout(
3508
- () => resolve({__type: TransactionStatus.TIMED_OUT, timeoutMs}),
3509
- timeoutMs,
3510
- );
3590
+ };
3591
+ (async () => {
3592
+ let currentBlockHeight = await checkBlockHeight();
3593
+ if (done) return;
3594
+ while (currentBlockHeight <= lastValidBlockHeight) {
3595
+ await sleep(1000);
3596
+ if (done) return;
3597
+ currentBlockHeight = await checkBlockHeight();
3598
+ if (done) return;
3599
+ }
3600
+ resolve({__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED});
3601
+ })();
3602
+ });
3603
+ const {abortConfirmation, confirmationPromise} =
3604
+ this.getTransactionConfirmationPromise({commitment, signature});
3605
+ let result: RpcResponseAndContext<SignatureResult>;
3606
+ try {
3607
+ const outcome = await Promise.race([confirmationPromise, expiryPromise]);
3608
+ if (outcome.__type === TransactionStatus.PROCESSED) {
3609
+ result = outcome.response;
3511
3610
  } else {
3512
- let config =
3513
- strategy as BlockheightBasedTransactionConfirmationStrategy;
3514
- const checkBlockHeight = async () => {
3515
- try {
3516
- const blockHeight = await this.getBlockHeight(commitment);
3517
- return blockHeight;
3518
- } catch (_e) {
3519
- return -1;
3611
+ throw new TransactionExpiredBlockheightExceededError(signature);
3612
+ }
3613
+ } finally {
3614
+ done = true;
3615
+ abortConfirmation();
3616
+ }
3617
+ return result;
3618
+ }
3619
+
3620
+ private async confirmTransactionUsingDurableNonceStrategy({
3621
+ commitment,
3622
+ strategy: {minContextSlot, nonceAccountPubkey, nonceValue, signature},
3623
+ }: {
3624
+ commitment?: Commitment;
3625
+ strategy: DurableNonceTransactionConfirmationStrategy;
3626
+ }) {
3627
+ let done: boolean = false;
3628
+ const expiryPromise = new Promise<{
3629
+ __type: TransactionStatus.NONCE_INVALID;
3630
+ slotInWhichNonceDidAdvance: number | null;
3631
+ }>(resolve => {
3632
+ let currentNonceValue: string | undefined = nonceValue;
3633
+ let lastCheckedSlot: number | null = null;
3634
+ const getCurrentNonceValue = async () => {
3635
+ try {
3636
+ const {context, value: nonceAccount} = await this.getNonceAndContext(
3637
+ nonceAccountPubkey,
3638
+ {
3639
+ commitment,
3640
+ minContextSlot,
3641
+ },
3642
+ );
3643
+ lastCheckedSlot = context.slot;
3644
+ return nonceAccount?.nonce;
3645
+ } catch (e) {
3646
+ // If for whatever reason we can't reach/read the nonce
3647
+ // account, just keep using the last-known value.
3648
+ return currentNonceValue;
3649
+ }
3650
+ };
3651
+ (async () => {
3652
+ currentNonceValue = await getCurrentNonceValue();
3653
+ if (done) return;
3654
+ while (
3655
+ true // eslint-disable-line no-constant-condition
3656
+ ) {
3657
+ if (nonceValue !== currentNonceValue) {
3658
+ resolve({
3659
+ __type: TransactionStatus.NONCE_INVALID,
3660
+ slotInWhichNonceDidAdvance: lastCheckedSlot,
3661
+ });
3662
+ return;
3520
3663
  }
3521
- };
3522
- (async () => {
3523
- let currentBlockHeight = await checkBlockHeight();
3664
+ await sleep(2000);
3524
3665
  if (done) return;
3525
- while (currentBlockHeight <= config.lastValidBlockHeight) {
3526
- await sleep(1000);
3527
- if (done) return;
3528
- currentBlockHeight = await checkBlockHeight();
3529
- if (done) return;
3530
- }
3531
- resolve({__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED});
3532
- })();
3533
- }
3666
+ currentNonceValue = await getCurrentNonceValue();
3667
+ if (done) return;
3668
+ }
3669
+ })();
3534
3670
  });
3535
-
3671
+ const {abortConfirmation, confirmationPromise} =
3672
+ this.getTransactionConfirmationPromise({commitment, signature});
3536
3673
  let result: RpcResponseAndContext<SignatureResult>;
3537
3674
  try {
3538
3675
  const outcome = await Promise.race([confirmationPromise, expiryPromise]);
3539
- switch (outcome.__type) {
3540
- case TransactionStatus.BLOCKHEIGHT_EXCEEDED:
3541
- throw new TransactionExpiredBlockheightExceededError(rawSignature);
3542
- case TransactionStatus.PROCESSED:
3543
- result = outcome.response;
3676
+ if (outcome.__type === TransactionStatus.PROCESSED) {
3677
+ result = outcome.response;
3678
+ } else {
3679
+ // Double check that the transaction is indeed unconfirmed.
3680
+ let signatureStatus:
3681
+ | RpcResponseAndContext<SignatureStatus | null>
3682
+ | null
3683
+ | undefined;
3684
+ while (
3685
+ true // eslint-disable-line no-constant-condition
3686
+ ) {
3687
+ const status = await this.getSignatureStatus(signature);
3688
+ if (status == null) {
3689
+ break;
3690
+ }
3691
+ if (
3692
+ status.context.slot <
3693
+ (outcome.slotInWhichNonceDidAdvance ?? minContextSlot)
3694
+ ) {
3695
+ await sleep(400);
3696
+ continue;
3697
+ }
3698
+ signatureStatus = status;
3544
3699
  break;
3545
- case TransactionStatus.TIMED_OUT:
3546
- throw new TransactionExpiredTimeoutError(
3547
- rawSignature,
3548
- outcome.timeoutMs / 1000,
3549
- );
3700
+ }
3701
+ if (signatureStatus?.value) {
3702
+ const commitmentForStatus = commitment || 'finalized';
3703
+ const {confirmationStatus} = signatureStatus.value;
3704
+ switch (commitmentForStatus) {
3705
+ case 'processed':
3706
+ case 'recent':
3707
+ if (
3708
+ confirmationStatus !== 'processed' &&
3709
+ confirmationStatus !== 'confirmed' &&
3710
+ confirmationStatus !== 'finalized'
3711
+ ) {
3712
+ throw new TransactionExpiredNonceInvalidError(signature);
3713
+ }
3714
+ break;
3715
+ case 'confirmed':
3716
+ case 'single':
3717
+ case 'singleGossip':
3718
+ if (
3719
+ confirmationStatus !== 'confirmed' &&
3720
+ confirmationStatus !== 'finalized'
3721
+ ) {
3722
+ throw new TransactionExpiredNonceInvalidError(signature);
3723
+ }
3724
+ break;
3725
+ case 'finalized':
3726
+ case 'max':
3727
+ case 'root':
3728
+ if (confirmationStatus !== 'finalized') {
3729
+ throw new TransactionExpiredNonceInvalidError(signature);
3730
+ }
3731
+ break;
3732
+ default:
3733
+ // Exhaustive switch.
3734
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3735
+ ((_: never) => {})(commitmentForStatus);
3736
+ }
3737
+ result = {
3738
+ context: signatureStatus.context,
3739
+ value: {err: signatureStatus.value.err},
3740
+ };
3741
+ } else {
3742
+ throw new TransactionExpiredNonceInvalidError(signature);
3743
+ }
3550
3744
  }
3551
3745
  } finally {
3552
- clearTimeout(timeoutId);
3553
- if (disposeSignatureSubscriptionStateChangeObserver) {
3554
- disposeSignatureSubscriptionStateChangeObserver();
3746
+ done = true;
3747
+ abortConfirmation();
3748
+ }
3749
+ return result;
3750
+ }
3751
+
3752
+ private async confirmTransactionUsingLegacyTimeoutStrategy({
3753
+ commitment,
3754
+ signature,
3755
+ }: {
3756
+ commitment?: Commitment;
3757
+ signature: string;
3758
+ }) {
3759
+ let timeoutId;
3760
+ const expiryPromise = new Promise<{
3761
+ __type: TransactionStatus.TIMED_OUT;
3762
+ timeoutMs: number;
3763
+ }>(resolve => {
3764
+ let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
3765
+ switch (commitment) {
3766
+ case 'processed':
3767
+ case 'recent':
3768
+ case 'single':
3769
+ case 'confirmed':
3770
+ case 'singleGossip': {
3771
+ timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
3772
+ break;
3773
+ }
3774
+ // exhaust enums to ensure full coverage
3775
+ case 'finalized':
3776
+ case 'max':
3777
+ case 'root':
3555
3778
  }
3556
- if (signatureSubscriptionId) {
3557
- this.removeSignatureListener(signatureSubscriptionId);
3779
+ timeoutId = setTimeout(
3780
+ () => resolve({__type: TransactionStatus.TIMED_OUT, timeoutMs}),
3781
+ timeoutMs,
3782
+ );
3783
+ });
3784
+ const {abortConfirmation, confirmationPromise} =
3785
+ this.getTransactionConfirmationPromise({
3786
+ commitment,
3787
+ signature,
3788
+ });
3789
+ let result: RpcResponseAndContext<SignatureResult>;
3790
+ try {
3791
+ const outcome = await Promise.race([confirmationPromise, expiryPromise]);
3792
+ if (outcome.__type === TransactionStatus.PROCESSED) {
3793
+ result = outcome.response;
3794
+ } else {
3795
+ throw new TransactionExpiredTimeoutError(
3796
+ signature,
3797
+ outcome.timeoutMs / 1000,
3798
+ );
3558
3799
  }
3800
+ } finally {
3801
+ clearTimeout(timeoutId);
3802
+ abortConfirmation();
3559
3803
  }
3560
3804
  return result;
3561
3805
  }
@@ -4708,11 +4952,11 @@ export class Connection {
4708
4952
  */
4709
4953
  async getNonceAndContext(
4710
4954
  nonceAccount: PublicKey,
4711
- commitment?: Commitment,
4955
+ commitmentOrConfig?: Commitment | GetNonceAndContextConfig,
4712
4956
  ): Promise<RpcResponseAndContext<NonceAccount | null>> {
4713
4957
  const {context, value: accountInfo} = await this.getAccountInfoAndContext(
4714
4958
  nonceAccount,
4715
- commitment,
4959
+ commitmentOrConfig,
4716
4960
  );
4717
4961
 
4718
4962
  let value = null;
@@ -4731,9 +4975,9 @@ export class Connection {
4731
4975
  */
4732
4976
  async getNonce(
4733
4977
  nonceAccount: PublicKey,
4734
- commitment?: Commitment,
4978
+ commitmentOrConfig?: Commitment | GetNonceConfig,
4735
4979
  ): Promise<NonceAccount | null> {
4736
- return await this.getNonceAndContext(nonceAccount, commitment)
4980
+ return await this.getNonceAndContext(nonceAccount, commitmentOrConfig)
4737
4981
  .then(x => x.value)
4738
4982
  .catch(e => {
4739
4983
  throw new Error(
@@ -1,7 +1,6 @@
1
1
  import * as BufferLayout from '@solana/buffer-layout';
2
2
  import {Buffer} from 'buffer';
3
3
 
4
- import type {Blockhash} from './blockhash';
5
4
  import * as Layout from './layout';
6
5
  import {PublicKey} from './publickey';
7
6
  import type {FeeCalculator} from './fee-calculator';
@@ -36,9 +35,14 @@ const NonceAccountLayout = BufferLayout.struct<
36
35
 
37
36
  export const NONCE_ACCOUNT_LENGTH = NonceAccountLayout.span;
38
37
 
38
+ /**
39
+ * A durable nonce is a 32 byte value encoded as a base58 string.
40
+ */
41
+ export type DurableNonce = string;
42
+
39
43
  type NonceAccountArgs = {
40
44
  authorizedPubkey: PublicKey;
41
- nonce: Blockhash;
45
+ nonce: DurableNonce;
42
46
  feeCalculator: FeeCalculator;
43
47
  };
44
48
 
@@ -47,7 +51,7 @@ type NonceAccountArgs = {
47
51
  */
48
52
  export class NonceAccount {
49
53
  authorizedPubkey: PublicKey;
50
- nonce: Blockhash;
54
+ nonce: DurableNonce;
51
55
  feeCalculator: FeeCalculator;
52
56
 
53
57
  /**
@@ -33,3 +33,16 @@ export class TransactionExpiredTimeoutError extends Error {
33
33
  Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', {
34
34
  value: 'TransactionExpiredTimeoutError',
35
35
  });
36
+
37
+ export class TransactionExpiredNonceInvalidError extends Error {
38
+ signature: string;
39
+
40
+ constructor(signature: string) {
41
+ super(`Signature ${signature} has expired: the nonce is no longer valid.`);
42
+ this.signature = signature;
43
+ }
44
+ }
45
+
46
+ Object.defineProperty(TransactionExpiredNonceInvalidError.prototype, 'name', {
47
+ value: 'TransactionExpiredNonceInvalidError',
48
+ });
@@ -22,6 +22,7 @@ export const enum TransactionStatus {
22
22
  BLOCKHEIGHT_EXCEEDED,
23
23
  PROCESSED,
24
24
  TIMED_OUT,
25
+ NONCE_INVALID,
25
26
  }
26
27
 
27
28
  /**
@@ -145,7 +146,9 @@ export type TransactionCtorFields_DEPRECATED = {
145
146
  export type TransactionCtorFields = TransactionCtorFields_DEPRECATED;
146
147
 
147
148
  /**
148
- * List of Transaction object fields that may be initialized at construction
149
+ * Blockhash-based transactions have a lifetime that are defined by
150
+ * the blockhash they include. Any transaction whose blockhash is
151
+ * too old will be rejected.
149
152
  */
150
153
  export type TransactionBlockhashCtor = {
151
154
  /** The transaction fee payer */
@@ -158,6 +161,18 @@ export type TransactionBlockhashCtor = {
158
161
  lastValidBlockHeight: number;
159
162
  };
160
163
 
164
+ /**
165
+ * Use these options to construct a durable nonce transaction.
166
+ */
167
+ export type TransactionNonceCtor = {
168
+ /** The transaction fee payer */
169
+ feePayer?: PublicKey | null;
170
+ minContextSlot: number;
171
+ nonceInfo: NonceInformation;
172
+ /** One or more signatures */
173
+ signatures?: Array<SignaturePubkeyPair>;
174
+ };
175
+
161
176
  /**
162
177
  * Nonce information to be used to build an offline Transaction.
163
178
  */
@@ -228,6 +243,15 @@ export class Transaction {
228
243
  */
229
244
  nonceInfo?: NonceInformation;
230
245
 
246
+ /**
247
+ * If this is a nonce transaction this represents the minimum slot from which
248
+ * to evaluate if the nonce has advanced when attempting to confirm the
249
+ * transaction. This protects against a case where the transaction confirmation
250
+ * logic loads the nonce account from an old slot and assumes the mismatch in
251
+ * nonce value implies that the nonce has been advanced.
252
+ */
253
+ minNonceContextSlot?: number;
254
+
231
255
  /**
232
256
  * @internal
233
257
  */
@@ -241,6 +265,9 @@ export class Transaction {
241
265
  // Construct a transaction with a blockhash and lastValidBlockHeight
242
266
  constructor(opts?: TransactionBlockhashCtor);
243
267
 
268
+ // Construct a transaction using a durable nonce
269
+ constructor(opts?: TransactionNonceCtor);
270
+
244
271
  /**
245
272
  * @deprecated `TransactionCtorFields` has been deprecated and will be removed in a future version.
246
273
  * Please supply a `TransactionBlockhashCtor` instead.
@@ -251,7 +278,10 @@ export class Transaction {
251
278
  * Construct an empty Transaction
252
279
  */
253
280
  constructor(
254
- opts?: TransactionBlockhashCtor | TransactionCtorFields_DEPRECATED,
281
+ opts?:
282
+ | TransactionBlockhashCtor
283
+ | TransactionNonceCtor
284
+ | TransactionCtorFields_DEPRECATED,
255
285
  ) {
256
286
  if (!opts) {
257
287
  return;
@@ -262,7 +292,13 @@ export class Transaction {
262
292
  if (opts.signatures) {
263
293
  this.signatures = opts.signatures;
264
294
  }
265
- if (Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')) {
295
+ if (Object.prototype.hasOwnProperty.call(opts, 'nonceInfo')) {
296
+ const {minContextSlot, nonceInfo} = opts as TransactionNonceCtor;
297
+ this.minNonceContextSlot = minContextSlot;
298
+ this.nonceInfo = nonceInfo;
299
+ } else if (
300
+ Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')
301
+ ) {
266
302
  const {blockhash, lastValidBlockHeight} =
267
303
  opts as TransactionBlockhashCtor;
268
304
  this.recentBlockhash = blockhash;