@solana/web3.js 0.0.0-next → 0.0.0-pr-29130

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 (74) hide show
  1. package/README.md +24 -25
  2. package/lib/index.browser.cjs.js +4583 -4238
  3. package/lib/index.browser.cjs.js.map +1 -1
  4. package/lib/index.browser.esm.js +4565 -4238
  5. package/lib/index.browser.esm.js.map +1 -1
  6. package/lib/index.cjs.js +7072 -3604
  7. package/lib/index.cjs.js.map +1 -1
  8. package/lib/index.d.ts +3516 -2420
  9. package/lib/index.esm.js +7046 -3601
  10. package/lib/index.esm.js.map +1 -1
  11. package/lib/index.iife.js +22171 -27053
  12. package/lib/index.iife.js.map +1 -1
  13. package/lib/index.iife.min.js +8 -33
  14. package/lib/index.iife.min.js.map +1 -1
  15. package/lib/index.native.js +10407 -0
  16. package/lib/index.native.js.map +1 -0
  17. package/package.json +36 -36
  18. package/src/__forks__/browser/fetch-impl.ts +4 -0
  19. package/src/__forks__/react-native/fetch-impl.ts +4 -0
  20. package/src/account-data.ts +39 -0
  21. package/src/account.ts +20 -11
  22. package/src/bpf-loader.ts +2 -2
  23. package/src/connection.ts +2303 -635
  24. package/src/epoch-schedule.ts +1 -1
  25. package/src/errors.ts +41 -0
  26. package/src/fee-calculator.ts +2 -0
  27. package/src/fetch-impl.ts +13 -0
  28. package/src/index.ts +3 -10
  29. package/src/keypair.ts +20 -25
  30. package/src/layout.ts +45 -4
  31. package/src/loader.ts +3 -3
  32. package/src/message/account-keys.ts +79 -0
  33. package/src/message/compiled-keys.ts +165 -0
  34. package/src/message/index.ts +47 -0
  35. package/src/{message.ts → message/legacy.ts} +95 -40
  36. package/src/message/v0.ts +496 -0
  37. package/src/message/versioned.ts +36 -0
  38. package/src/nonce-account.ts +8 -4
  39. package/src/programs/address-lookup-table/index.ts +435 -0
  40. package/src/programs/address-lookup-table/state.ts +84 -0
  41. package/src/programs/compute-budget.ts +281 -0
  42. package/src/{ed25519-program.ts → programs/ed25519.ts} +6 -6
  43. package/src/programs/index.ts +7 -0
  44. package/src/{secp256k1-program.ts → programs/secp256k1.ts} +15 -16
  45. package/src/{stake-program.ts → programs/stake.ts} +7 -7
  46. package/src/{system-program.ts → programs/system.ts} +55 -18
  47. package/src/{vote-program.ts → programs/vote.ts} +137 -9
  48. package/src/publickey.ts +37 -79
  49. package/src/transaction/constants.ts +12 -0
  50. package/src/transaction/expiry-custom-errors.ts +48 -0
  51. package/src/transaction/index.ts +5 -0
  52. package/src/{transaction.ts → transaction/legacy.ts} +162 -67
  53. package/src/transaction/message.ts +140 -0
  54. package/src/transaction/versioned.ts +126 -0
  55. package/src/{util → utils}/assert.ts +0 -0
  56. package/src/utils/bigint.ts +43 -0
  57. package/src/{util → utils}/borsh-schema.ts +0 -0
  58. package/src/{util → utils}/cluster.ts +0 -0
  59. package/src/utils/ed25519.ts +46 -0
  60. package/src/utils/index.ts +5 -0
  61. package/src/utils/makeWebsocketUrl.ts +26 -0
  62. package/src/{util → utils}/promise-timeout.ts +0 -0
  63. package/src/utils/secp256k1.ts +18 -0
  64. package/src/utils/send-and-confirm-raw-transaction.ts +105 -0
  65. package/src/utils/send-and-confirm-transaction.ts +98 -0
  66. package/src/{util → utils}/shortvec-encoding.ts +0 -0
  67. package/src/{util → utils}/sleep.ts +0 -0
  68. package/src/{util → utils}/to-buffer.ts +0 -0
  69. package/src/validator-info.ts +4 -6
  70. package/src/vote-account.ts +1 -1
  71. package/src/agent-manager.ts +0 -44
  72. package/src/util/send-and-confirm-raw-transaction.ts +0 -46
  73. package/src/util/send-and-confirm-transaction.ts +0 -50
  74. package/src/util/url.ts +0 -18
package/src/connection.ts CHANGED
@@ -1,8 +1,10 @@
1
+ import Agent from 'agentkeepalive';
1
2
  import bs58 from 'bs58';
2
3
  import {Buffer} from 'buffer';
3
- import crossFetch from 'cross-fetch';
4
4
  // @ts-ignore
5
5
  import fastStableStringify from 'fast-stable-stringify';
6
+ import type {Agent as HttpAgent} from 'http';
7
+ import {Agent as HttpsAgent} from 'https';
6
8
  import {
7
9
  type as pick,
8
10
  number,
@@ -24,22 +26,32 @@ import {
24
26
  import type {Struct} from 'superstruct';
25
27
  import {Client as RpcWebSocketClient} from 'rpc-websockets';
26
28
  import RpcClient from 'jayson/lib/client/browser';
27
- import {IWSRequestParams} from 'rpc-websockets/dist/lib/client';
29
+ import {JSONRPCError} from 'jayson';
28
30
 
29
- import {AgentManager} from './agent-manager';
30
31
  import {EpochSchedule} from './epoch-schedule';
31
- import {SendTransactionError} from './errors';
32
- import {NonceAccount} from './nonce-account';
32
+ import {SendTransactionError, SolanaJSONRPCError} from './errors';
33
+ import fetchImpl, {Response} from './fetch-impl';
34
+ import {DurableNonce, NonceAccount} from './nonce-account';
33
35
  import {PublicKey} from './publickey';
34
36
  import {Signer} from './keypair';
35
37
  import {MS_PER_SLOT} from './timing';
36
- import {Transaction} from './transaction';
37
- import {Message} from './message';
38
- import assert from './util/assert';
39
- import {sleep} from './util/sleep';
40
- import {promiseTimeout} from './util/promise-timeout';
41
- import {toBuffer} from './util/to-buffer';
42
- import {makeWebsocketUrl} from './util/url';
38
+ import {
39
+ Transaction,
40
+ TransactionStatus,
41
+ TransactionVersion,
42
+ VersionedTransaction,
43
+ } from './transaction';
44
+ import {Message, MessageHeader, MessageV0, VersionedMessage} from './message';
45
+ import {AddressLookupTableAccount} from './programs/address-lookup-table/state';
46
+ import assert from './utils/assert';
47
+ import {sleep} from './utils/sleep';
48
+ import {toBuffer} from './utils/to-buffer';
49
+ import {
50
+ TransactionExpiredBlockheightExceededError,
51
+ TransactionExpiredNonceInvalidError,
52
+ TransactionExpiredTimeoutError,
53
+ } from './transaction/expiry-custom-errors';
54
+ import {makeWebsocketUrl} from './utils/makeWebsocketUrl';
43
55
  import type {Blockhash} from './blockhash';
44
56
  import type {FeeCalculator} from './fee-calculator';
45
57
  import type {TransactionSignature} from './transaction';
@@ -65,15 +77,32 @@ const BufferFromRawAccountData = coerce(
65
77
  */
66
78
  export const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
67
79
 
80
+ /**
81
+ * HACK.
82
+ * Copied from rpc-websockets/dist/lib/client.
83
+ * Otherwise, `yarn build` fails with:
84
+ * https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d
85
+ */
86
+ interface IWSRequestParams {
87
+ [x: string]: any;
88
+ [x: number]: any;
89
+ }
90
+
68
91
  type ClientSubscriptionId = number;
69
92
  /** @internal */ type ServerSubscriptionId = number;
70
93
  /** @internal */ type SubscriptionConfigHash = string;
71
94
  /** @internal */ type SubscriptionDisposeFn = () => Promise<void>;
95
+ /** @internal */ type SubscriptionStateChangeCallback = (
96
+ nextState: StatefulSubscription['state'],
97
+ ) => void;
98
+ /** @internal */ type SubscriptionStateChangeDisposeFn = () => void;
72
99
  /**
73
100
  * @internal
74
- * Every subscription has a list of callers interested in publishes.
101
+ * Every subscription contains the args used to open the subscription with
102
+ * the server, and a list of callers interested in notifications.
75
103
  */
76
104
  type BaseSubscription<TMethod = SubscriptionConfig['method']> = Readonly<{
105
+ args: IWSRequestParams;
77
106
  callbacks: Set<Extract<SubscriptionConfig, {method: TMethod}>['callback']>;
78
107
  }>;
79
108
  /**
@@ -114,62 +143,43 @@ type StatefulSubscription = Readonly<
114
143
  }
115
144
  >;
116
145
  /**
117
- * Different RPC subscription methods accept different params.
146
+ * A type that encapsulates a subscription's RPC method
147
+ * names and notification (callback) signature.
118
148
  */
119
149
  type SubscriptionConfig = Readonly<
120
150
  | {
121
151
  callback: AccountChangeCallback;
122
152
  method: 'accountSubscribe';
123
- params: Readonly<{
124
- commitment?: Commitment; // Must default to 'finalized'
125
- publicKey: string; // PublicKey of the account as a base 58 string
126
- }>;
127
153
  unsubscribeMethod: 'accountUnsubscribe';
128
154
  }
129
155
  | {
130
156
  callback: LogsCallback;
131
157
  method: 'logsSubscribe';
132
- params: Readonly<{
133
- commitment?: Commitment; // Must default to 'finalized'
134
- filter: LogsFilter;
135
- }>;
136
158
  unsubscribeMethod: 'logsUnsubscribe';
137
159
  }
138
160
  | {
139
161
  callback: ProgramAccountChangeCallback;
140
162
  method: 'programSubscribe';
141
- params: Readonly<{
142
- commitment?: Commitment; // Must default to 'finalized'
143
- filters?: GetProgramAccountsFilter[];
144
- programId: string; // PublicKey of the program as a base 58 string
145
- }>;
146
163
  unsubscribeMethod: 'programUnsubscribe';
147
164
  }
148
165
  | {
149
166
  callback: RootChangeCallback;
150
167
  method: 'rootSubscribe';
151
- params: undefined;
152
168
  unsubscribeMethod: 'rootUnsubscribe';
153
169
  }
154
170
  | {
155
171
  callback: SignatureSubscriptionCallback;
156
172
  method: 'signatureSubscribe';
157
- params: Readonly<{
158
- signature: TransactionSignature; // TransactionSignature as a base 58 string
159
- options?: SignatureSubscriptionOptions;
160
- }>;
161
173
  unsubscribeMethod: 'signatureUnsubscribe';
162
174
  }
163
175
  | {
164
176
  callback: SlotChangeCallback;
165
177
  method: 'slotSubscribe';
166
- params: undefined;
167
178
  unsubscribeMethod: 'slotUnsubscribe';
168
179
  }
169
180
  | {
170
181
  callback: SlotUpdateCallback;
171
182
  method: 'slotsUpdatesSubscribe';
172
- params: undefined;
173
183
  unsubscribeMethod: 'slotsUpdatesUnsubscribe';
174
184
  }
175
185
  >;
@@ -180,23 +190,24 @@ type SubscriptionConfig = Readonly<
180
190
  type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
181
191
  ? Omit<T, K>
182
192
  : never;
183
- type Subscription = BaseSubscription &
184
- StatefulSubscription &
185
- DistributiveOmit<SubscriptionConfig, 'callback'>;
186
-
187
193
  /**
188
194
  * @internal
195
+ * This type represents a single subscribable 'topic.' It's made up of:
196
+ *
197
+ * - The args used to open the subscription with the server,
198
+ * - The state of the subscription, in terms of its connectedness, and
199
+ * - The set of callbacks to call when the server publishes notifications
200
+ *
201
+ * This record gets indexed by `SubscriptionConfigHash` and is used to
202
+ * set up subscriptions, fan out notifications, and track subscription state.
189
203
  */
190
- function hashSubscriptionConfig({
191
- method,
192
- params,
193
- }: SubscriptionConfig): SubscriptionConfigHash {
194
- return fastStableStringify([method, params], true /* isArrayProp */);
195
- }
204
+ type Subscription = BaseSubscription &
205
+ StatefulSubscription &
206
+ DistributiveOmit<SubscriptionConfig, 'callback'>;
196
207
 
197
- type RpcRequest = (methodName: string, args: Array<any>) => any;
208
+ type RpcRequest = (methodName: string, args: Array<any>) => Promise<any>;
198
209
 
199
- type RpcBatchRequest = (requests: RpcParams[]) => any;
210
+ type RpcBatchRequest = (requests: RpcParams[]) => Promise<any[]>;
200
211
 
201
212
  /**
202
213
  * @internal
@@ -231,6 +242,8 @@ export type SendOptions = {
231
242
  preflightCommitment?: Commitment;
232
243
  /** Maximum number of times for the RPC node to retry sending the transaction to the leader. */
233
244
  maxRetries?: number;
245
+ /** The minimum slot that the request can be evaluated at */
246
+ minContextSlot?: number;
234
247
  };
235
248
 
236
249
  /**
@@ -245,6 +258,8 @@ export type ConfirmOptions = {
245
258
  preflightCommitment?: Commitment;
246
259
  /** Maximum number of times for the RPC node to retry sending the transaction to the leader. */
247
260
  maxRetries?: number;
261
+ /** The minimum slot that the request can be evaluated at */
262
+ minContextSlot?: number;
248
263
  };
249
264
 
250
265
  /**
@@ -275,6 +290,8 @@ export type SignaturesForAddressOptions = {
275
290
  until?: TransactionSignature;
276
291
  /** Maximum transaction signatures to return (between 1 and 1,000, default: 1,000). */
277
292
  limit?: number;
293
+ /** The minimum slot that the request can be evaluated at */
294
+ minContextSlot?: number;
278
295
  };
279
296
 
280
297
  /**
@@ -287,6 +304,74 @@ export type RpcResponseAndContext<T> = {
287
304
  value: T;
288
305
  };
289
306
 
307
+ export type BlockhashWithExpiryBlockHeight = Readonly<{
308
+ blockhash: Blockhash;
309
+ lastValidBlockHeight: number;
310
+ }>;
311
+
312
+ /**
313
+ * A strategy for confirming transactions that uses the last valid
314
+ * block height for a given blockhash to check for transaction expiration.
315
+ */
316
+ export type BlockheightBasedTransactionConfirmationStrategy =
317
+ BaseTransactionConfirmationStrategy & BlockhashWithExpiryBlockHeight;
318
+
319
+ /**
320
+ * A strategy for confirming durable nonce transactions.
321
+ */
322
+ export type DurableNonceTransactionConfirmationStrategy =
323
+ BaseTransactionConfirmationStrategy & {
324
+ /**
325
+ * The lowest slot at which to fetch the nonce value from the
326
+ * nonce account. This should be no lower than the slot at
327
+ * which the last-known value of the nonce was fetched.
328
+ */
329
+ minContextSlot: number;
330
+ /**
331
+ * The account where the current value of the nonce is stored.
332
+ */
333
+ nonceAccountPubkey: PublicKey;
334
+ /**
335
+ * The nonce value that was used to sign the transaction
336
+ * for which confirmation is being sought.
337
+ */
338
+ nonceValue: DurableNonce;
339
+ };
340
+
341
+ /**
342
+ * Properties shared by all transaction confirmation strategies
343
+ */
344
+ export type BaseTransactionConfirmationStrategy = Readonly<{
345
+ /** A signal that, when aborted, cancels any outstanding transaction confirmation operations */
346
+ abortSignal?: AbortSignal;
347
+ signature: TransactionSignature;
348
+ }>;
349
+
350
+ /* @internal */
351
+ function assertEndpointUrl(putativeUrl: string) {
352
+ if (/^https?:/.test(putativeUrl) === false) {
353
+ throw new TypeError('Endpoint URL must start with `http:` or `https:`.');
354
+ }
355
+ return putativeUrl;
356
+ }
357
+
358
+ /** @internal */
359
+ function extractCommitmentFromConfig<TConfig>(
360
+ commitmentOrConfig?: Commitment | ({commitment?: Commitment} & TConfig),
361
+ ) {
362
+ let commitment: Commitment | undefined;
363
+ let config: Omit<TConfig, 'commitment'> | undefined;
364
+ if (typeof commitmentOrConfig === 'string') {
365
+ commitment = commitmentOrConfig;
366
+ } else if (commitmentOrConfig) {
367
+ const {commitment: specifiedCommitment, ...specifiedConfig} =
368
+ commitmentOrConfig;
369
+ commitment = specifiedCommitment;
370
+ config = specifiedConfig;
371
+ }
372
+ return {commitment, config};
373
+ }
374
+
290
375
  /**
291
376
  * @internal
292
377
  */
@@ -353,6 +438,32 @@ function notificationResultAndContext<T, U>(value: Struct<T, U>) {
353
438
  });
354
439
  }
355
440
 
441
+ /**
442
+ * @internal
443
+ */
444
+ function versionedMessageFromResponse(
445
+ version: TransactionVersion | undefined,
446
+ response: MessageResponse,
447
+ ): VersionedMessage {
448
+ if (version === 0) {
449
+ return new MessageV0({
450
+ header: response.header,
451
+ staticAccountKeys: response.accountKeys.map(
452
+ accountKey => new PublicKey(accountKey),
453
+ ),
454
+ recentBlockhash: response.recentBlockhash,
455
+ compiledInstructions: response.instructions.map(ix => ({
456
+ programIdIndex: ix.programIdIndex,
457
+ accountKeyIndexes: ix.accounts,
458
+ data: bs58.decode(ix.data),
459
+ })),
460
+ addressTableLookups: response.addressTableLookups!,
461
+ });
462
+ } else {
463
+ return new Message(response);
464
+ }
465
+ }
466
+
356
467
  /**
357
468
  * The level of commitment desired when querying state
358
469
  * <pre>
@@ -389,6 +500,158 @@ export type Finality = 'confirmed' | 'finalized';
389
500
  */
390
501
  export type LargestAccountsFilter = 'circulating' | 'nonCirculating';
391
502
 
503
+ /**
504
+ * Configuration object for changing `getAccountInfo` query behavior
505
+ */
506
+ export type GetAccountInfoConfig = {
507
+ /** The level of commitment desired */
508
+ commitment?: Commitment;
509
+ /** The minimum slot that the request can be evaluated at */
510
+ minContextSlot?: number;
511
+ /** Optional data slice to limit the returned account data */
512
+ dataSlice?: DataSlice;
513
+ };
514
+
515
+ /**
516
+ * Configuration object for changing `getBalance` query behavior
517
+ */
518
+ export type GetBalanceConfig = {
519
+ /** The level of commitment desired */
520
+ commitment?: Commitment;
521
+ /** The minimum slot that the request can be evaluated at */
522
+ minContextSlot?: number;
523
+ };
524
+
525
+ /**
526
+ * Configuration object for changing `getBlock` query behavior
527
+ */
528
+ export type GetBlockConfig = {
529
+ /** The level of finality desired */
530
+ commitment?: Finality;
531
+ /**
532
+ * Whether to populate the rewards array. If parameter not provided, the default includes rewards.
533
+ */
534
+ rewards?: boolean;
535
+ /**
536
+ * Level of transaction detail to return, either "full", "accounts", "signatures", or "none". If
537
+ * parameter not provided, the default detail level is "full". If "accounts" are requested,
538
+ * transaction details only include signatures and an annotated list of accounts in each
539
+ * transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances,
540
+ * pre_token_balances, and post_token_balances.
541
+ */
542
+ transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures';
543
+ };
544
+
545
+ /**
546
+ * Configuration object for changing `getBlock` query behavior
547
+ */
548
+ export type GetVersionedBlockConfig = {
549
+ /** The level of finality desired */
550
+ commitment?: Finality;
551
+ /** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */
552
+ maxSupportedTransactionVersion?: number;
553
+ /**
554
+ * Whether to populate the rewards array. If parameter not provided, the default includes rewards.
555
+ */
556
+ rewards?: boolean;
557
+ /**
558
+ * Level of transaction detail to return, either "full", "accounts", "signatures", or "none". If
559
+ * parameter not provided, the default detail level is "full". If "accounts" are requested,
560
+ * transaction details only include signatures and an annotated list of accounts in each
561
+ * transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances,
562
+ * pre_token_balances, and post_token_balances.
563
+ */
564
+ transactionDetails?: 'accounts' | 'full' | 'none' | 'signatures';
565
+ };
566
+
567
+ /**
568
+ * Configuration object for changing `getStakeMinimumDelegation` query behavior
569
+ */
570
+ export type GetStakeMinimumDelegationConfig = {
571
+ /** The level of commitment desired */
572
+ commitment?: Commitment;
573
+ };
574
+
575
+ /**
576
+ * Configuration object for changing `getBlockHeight` query behavior
577
+ */
578
+ export type GetBlockHeightConfig = {
579
+ /** The level of commitment desired */
580
+ commitment?: Commitment;
581
+ /** The minimum slot that the request can be evaluated at */
582
+ minContextSlot?: number;
583
+ };
584
+
585
+ /**
586
+ * Configuration object for changing `getEpochInfo` query behavior
587
+ */
588
+ export type GetEpochInfoConfig = {
589
+ /** The level of commitment desired */
590
+ commitment?: Commitment;
591
+ /** The minimum slot that the request can be evaluated at */
592
+ minContextSlot?: number;
593
+ };
594
+
595
+ /**
596
+ * Configuration object for changing `getInflationReward` query behavior
597
+ */
598
+ export type GetInflationRewardConfig = {
599
+ /** The level of commitment desired */
600
+ commitment?: Commitment;
601
+ /** An epoch for which the reward occurs. If omitted, the previous epoch will be used */
602
+ epoch?: number;
603
+ /** The minimum slot that the request can be evaluated at */
604
+ minContextSlot?: number;
605
+ };
606
+
607
+ /**
608
+ * Configuration object for changing `getLatestBlockhash` query behavior
609
+ */
610
+ export type GetLatestBlockhashConfig = {
611
+ /** The level of commitment desired */
612
+ commitment?: Commitment;
613
+ /** The minimum slot that the request can be evaluated at */
614
+ minContextSlot?: number;
615
+ };
616
+
617
+ /**
618
+ * Configuration object for changing `getSlot` query behavior
619
+ */
620
+ export type GetSlotConfig = {
621
+ /** The level of commitment desired */
622
+ commitment?: Commitment;
623
+ /** The minimum slot that the request can be evaluated at */
624
+ minContextSlot?: number;
625
+ };
626
+
627
+ /**
628
+ * Configuration object for changing `getSlotLeader` query behavior
629
+ */
630
+ export type GetSlotLeaderConfig = {
631
+ /** The level of commitment desired */
632
+ commitment?: Commitment;
633
+ /** The minimum slot that the request can be evaluated at */
634
+ minContextSlot?: number;
635
+ };
636
+
637
+ /**
638
+ * Configuration object for changing `getTransaction` query behavior
639
+ */
640
+ export type GetTransactionConfig = {
641
+ /** The level of finality desired */
642
+ commitment?: Finality;
643
+ };
644
+
645
+ /**
646
+ * Configuration object for changing `getTransaction` query behavior
647
+ */
648
+ export type GetVersionedTransactionConfig = {
649
+ /** The level of finality desired */
650
+ commitment?: Finality;
651
+ /** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */
652
+ maxSupportedTransactionVersion?: number;
653
+ };
654
+
392
655
  /**
393
656
  * Configuration object for changing `getLargestAccounts` query behavior
394
657
  */
@@ -596,13 +859,36 @@ export type SimulatedTransactionAccountInfo = {
596
859
  rentEpoch?: number;
597
860
  };
598
861
 
862
+ export type TransactionReturnDataEncoding = 'base64';
863
+
864
+ export type TransactionReturnData = {
865
+ programId: string;
866
+ data: [string, TransactionReturnDataEncoding];
867
+ };
868
+
869
+ export type SimulateTransactionConfig = {
870
+ /** Optional parameter used to enable signature verification before simulation */
871
+ sigVerify?: boolean;
872
+ /** Optional parameter used to replace the simulated transaction's recent blockhash with the latest blockhash */
873
+ replaceRecentBlockhash?: boolean;
874
+ /** Optional parameter used to set the commitment level when selecting the latest block */
875
+ commitment?: Commitment;
876
+ /** Optional parameter used to specify a list of account addresses to return post simulation state for */
877
+ accounts?: {
878
+ encoding: 'base64';
879
+ addresses: string[];
880
+ };
881
+ /** Optional parameter used to specify the minimum block slot that can be used for simulation */
882
+ minContextSlot?: number;
883
+ };
884
+
599
885
  export type SimulatedTransactionResponse = {
600
886
  err: TransactionError | string | null;
601
887
  logs: Array<string> | null;
602
888
  accounts?: (SimulatedTransactionAccountInfo | null)[] | null;
603
889
  unitsConsumed?: number;
890
+ returnData?: TransactionReturnData | null;
604
891
  };
605
-
606
892
  const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(
607
893
  pick({
608
894
  err: nullable(union([pick({}), string()])),
@@ -623,6 +909,14 @@ const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(
623
909
  ),
624
910
  ),
625
911
  unitsConsumed: optional(number()),
912
+ returnData: optional(
913
+ nullable(
914
+ pick({
915
+ programId: string(),
916
+ data: tuple([string(), literal('base64')]),
917
+ }),
918
+ ),
919
+ ),
626
920
  }),
627
921
  );
628
922
 
@@ -645,6 +939,14 @@ export type TokenBalance = {
645
939
  */
646
940
  export type ParsedConfirmedTransactionMeta = ParsedTransactionMeta;
647
941
 
942
+ /**
943
+ * Collection of addresses loaded by a transaction using address table lookups
944
+ */
945
+ export type LoadedAddresses = {
946
+ writable: Array<PublicKey>;
947
+ readonly: Array<PublicKey>;
948
+ };
949
+
648
950
  /**
649
951
  * Metadata for a parsed transaction on the ledger
650
952
  */
@@ -665,6 +967,10 @@ export type ParsedTransactionMeta = {
665
967
  postTokenBalances?: Array<TokenBalance> | null;
666
968
  /** The error result of transaction processing */
667
969
  err: TransactionError | null;
970
+ /** The collection of addresses loaded using address lookup tables */
971
+ loadedAddresses?: LoadedAddresses;
972
+ /** The compute units consumed after processing the transaction */
973
+ computeUnitsConsumed?: number;
668
974
  };
669
975
 
670
976
  export type CompiledInnerInstruction = {
@@ -692,6 +998,10 @@ export type ConfirmedTransactionMeta = {
692
998
  postTokenBalances?: Array<TokenBalance> | null;
693
999
  /** The error result of transaction processing */
694
1000
  err: TransactionError | null;
1001
+ /** The collection of addresses loaded using address lookup tables */
1002
+ loadedAddresses?: LoadedAddresses;
1003
+ /** The compute units consumed after processing the transaction */
1004
+ computeUnitsConsumed?: number;
695
1005
  };
696
1006
 
697
1007
  /**
@@ -713,8 +1023,42 @@ export type TransactionResponse = {
713
1023
  blockTime?: number | null;
714
1024
  };
715
1025
 
1026
+ /**
1027
+ * A processed transaction from the RPC API
1028
+ */
1029
+ export type VersionedTransactionResponse = {
1030
+ /** The slot during which the transaction was processed */
1031
+ slot: number;
1032
+ /** The transaction */
1033
+ transaction: {
1034
+ /** The transaction message */
1035
+ message: VersionedMessage;
1036
+ /** The transaction signatures */
1037
+ signatures: string[];
1038
+ };
1039
+ /** Metadata produced from the transaction */
1040
+ meta: ConfirmedTransactionMeta | null;
1041
+ /** The unix timestamp of when the transaction was processed */
1042
+ blockTime?: number | null;
1043
+ /** The transaction version */
1044
+ version?: TransactionVersion;
1045
+ };
1046
+
1047
+ /**
1048
+ * A processed transaction message from the RPC API
1049
+ */
1050
+ type MessageResponse = {
1051
+ accountKeys: string[];
1052
+ header: MessageHeader;
1053
+ instructions: CompiledInstruction[];
1054
+ recentBlockhash: string;
1055
+ addressTableLookups?: ParsedAddressTableLookup[];
1056
+ };
1057
+
716
1058
  /**
717
1059
  * A confirmed transaction on the ledger
1060
+ *
1061
+ * @deprecated Deprecated since Solana v1.8.0.
718
1062
  */
719
1063
  export type ConfirmedTransaction = {
720
1064
  /** The slot during which the transaction was processed */
@@ -749,6 +1093,8 @@ export type ParsedMessageAccount = {
749
1093
  signer: boolean;
750
1094
  /** Indicates if the account is writable for this transaction */
751
1095
  writable: boolean;
1096
+ /** Indicates if the account key came from the transaction or a lookup table */
1097
+ source?: 'transaction' | 'lookupTable';
752
1098
  };
753
1099
 
754
1100
  /**
@@ -763,6 +1109,18 @@ export type ParsedInstruction = {
763
1109
  parsed: any;
764
1110
  };
765
1111
 
1112
+ /**
1113
+ * A parsed address table lookup
1114
+ */
1115
+ export type ParsedAddressTableLookup = {
1116
+ /** Address lookup table account key */
1117
+ accountKey: PublicKey;
1118
+ /** Parsed instruction info */
1119
+ writableIndexes: number[];
1120
+ /** Parsed instruction info */
1121
+ readonlyIndexes: number[];
1122
+ };
1123
+
766
1124
  /**
767
1125
  * A parsed transaction message
768
1126
  */
@@ -773,6 +1131,8 @@ export type ParsedMessage = {
773
1131
  instructions: (ParsedInstruction | PartiallyDecodedInstruction)[];
774
1132
  /** Recent blockhash */
775
1133
  recentBlockhash: string;
1134
+ /** Address table lookups used to load additional accounts */
1135
+ addressTableLookups?: ParsedAddressTableLookup[] | null;
776
1136
  };
777
1137
 
778
1138
  /**
@@ -804,6 +1164,8 @@ export type ParsedTransactionWithMeta = {
804
1164
  meta: ParsedTransactionMeta | null;
805
1165
  /** The unix timestamp of when the transaction was processed */
806
1166
  blockTime?: number | null;
1167
+ /** The version of the transaction message */
1168
+ version?: TransactionVersion;
807
1169
  };
808
1170
 
809
1171
  /**
@@ -827,6 +1189,8 @@ export type BlockResponse = {
827
1189
  };
828
1190
  /** Metadata produced from the transaction */
829
1191
  meta: ConfirmedTransactionMeta | null;
1192
+ /** The transaction version */
1193
+ version?: TransactionVersion;
830
1194
  }>;
831
1195
  /** Vector of block rewards */
832
1196
  rewards?: Array<{
@@ -844,86 +1208,227 @@ export type BlockResponse = {
844
1208
  };
845
1209
 
846
1210
  /**
847
- * A ConfirmedBlock on the ledger
1211
+ * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts`
848
1212
  */
849
- export type ConfirmedBlock = {
1213
+ export type AccountsModeBlockResponse = VersionedAccountsModeBlockResponse;
1214
+
1215
+ /**
1216
+ * A processed block fetched from the RPC API where the `transactionDetails` mode is `none`
1217
+ */
1218
+ export type NoneModeBlockResponse = VersionedNoneModeBlockResponse;
1219
+
1220
+ /**
1221
+ * A block with parsed transactions
1222
+ */
1223
+ export type ParsedBlockResponse = {
850
1224
  /** Blockhash of this block */
851
1225
  blockhash: Blockhash;
852
1226
  /** Blockhash of this block's parent */
853
1227
  previousBlockhash: Blockhash;
854
1228
  /** Slot index of this block's parent */
855
1229
  parentSlot: number;
856
- /** Vector of transactions and status metas */
1230
+ /** Vector of transactions with status meta and original message */
857
1231
  transactions: Array<{
858
- transaction: Transaction;
859
- meta: ConfirmedTransactionMeta | null;
1232
+ /** The details of the transaction */
1233
+ transaction: ParsedTransaction;
1234
+ /** Metadata produced from the transaction */
1235
+ meta: ParsedTransactionMeta | null;
1236
+ /** The transaction version */
1237
+ version?: TransactionVersion;
860
1238
  }>;
861
1239
  /** Vector of block rewards */
862
1240
  rewards?: Array<{
1241
+ /** Public key of reward recipient */
863
1242
  pubkey: string;
1243
+ /** Reward value in lamports */
864
1244
  lamports: number;
1245
+ /** Account balance after reward is applied */
865
1246
  postBalance: number | null;
1247
+ /** Type of reward received */
866
1248
  rewardType: string | null;
867
1249
  }>;
868
1250
  /** The unix timestamp of when the block was processed */
869
1251
  blockTime: number | null;
1252
+ /** The number of blocks beneath this block */
1253
+ blockHeight: number | null;
870
1254
  };
871
1255
 
872
1256
  /**
873
- * A Block on the ledger with signatures only
1257
+ * A block with parsed transactions where the `transactionDetails` mode is `accounts`
874
1258
  */
875
- export type BlockSignatures = {
1259
+ export type ParsedAccountsModeBlockResponse = Omit<
1260
+ ParsedBlockResponse,
1261
+ 'transactions'
1262
+ > & {
1263
+ transactions: Array<
1264
+ Omit<ParsedBlockResponse['transactions'][number], 'transaction'> & {
1265
+ transaction: Pick<
1266
+ ParsedBlockResponse['transactions'][number]['transaction'],
1267
+ 'signatures'
1268
+ > & {
1269
+ accountKeys: ParsedMessageAccount[];
1270
+ };
1271
+ }
1272
+ >;
1273
+ };
1274
+
1275
+ /**
1276
+ * A block with parsed transactions where the `transactionDetails` mode is `none`
1277
+ */
1278
+ export type ParsedNoneModeBlockResponse = Omit<
1279
+ ParsedBlockResponse,
1280
+ 'transactions'
1281
+ >;
1282
+
1283
+ /**
1284
+ * A processed block fetched from the RPC API
1285
+ */
1286
+ export type VersionedBlockResponse = {
876
1287
  /** Blockhash of this block */
877
1288
  blockhash: Blockhash;
878
1289
  /** Blockhash of this block's parent */
879
1290
  previousBlockhash: Blockhash;
880
1291
  /** Slot index of this block's parent */
881
1292
  parentSlot: number;
882
- /** Vector of signatures */
883
- signatures: Array<string>;
1293
+ /** Vector of transactions with status meta and original message */
1294
+ transactions: Array<{
1295
+ /** The transaction */
1296
+ transaction: {
1297
+ /** The transaction message */
1298
+ message: VersionedMessage;
1299
+ /** The transaction signatures */
1300
+ signatures: string[];
1301
+ };
1302
+ /** Metadata produced from the transaction */
1303
+ meta: ConfirmedTransactionMeta | null;
1304
+ /** The transaction version */
1305
+ version?: TransactionVersion;
1306
+ }>;
1307
+ /** Vector of block rewards */
1308
+ rewards?: Array<{
1309
+ /** Public key of reward recipient */
1310
+ pubkey: string;
1311
+ /** Reward value in lamports */
1312
+ lamports: number;
1313
+ /** Account balance after reward is applied */
1314
+ postBalance: number | null;
1315
+ /** Type of reward received */
1316
+ rewardType: string | null;
1317
+ }>;
884
1318
  /** The unix timestamp of when the block was processed */
885
1319
  blockTime: number | null;
886
1320
  };
887
1321
 
888
1322
  /**
889
- * recent block production information
1323
+ * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts`
890
1324
  */
891
- export type BlockProduction = Readonly<{
892
- /** a dictionary of validator identities, as base-58 encoded strings. Value is a two element array containing the number of leader slots and the number of blocks produced */
893
- byIdentity: Readonly<Record<string, ReadonlyArray<number>>>;
894
- /** Block production slot range */
895
- range: Readonly<{
896
- /** first slot of the block production information (inclusive) */
897
- firstSlot: number;
898
- /** last slot of block production information (inclusive) */
899
- lastSlot: number;
900
- }>;
901
- }>;
902
-
903
- export type GetBlockProductionConfig = {
904
- /** Optional commitment level */
905
- commitment?: Commitment;
906
- /** Slot range to return block production for. If parameter not provided, defaults to current epoch. */
907
- range?: {
908
- /** first slot to return block production information for (inclusive) */
909
- firstSlot: number;
910
- /** last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot */
911
- lastSlot?: number;
912
- };
913
- /** Only return results for this validator identity (base-58 encoded) */
914
- identity?: string;
1325
+ export type VersionedAccountsModeBlockResponse = Omit<
1326
+ VersionedBlockResponse,
1327
+ 'transactions'
1328
+ > & {
1329
+ transactions: Array<
1330
+ Omit<VersionedBlockResponse['transactions'][number], 'transaction'> & {
1331
+ transaction: Pick<
1332
+ VersionedBlockResponse['transactions'][number]['transaction'],
1333
+ 'signatures'
1334
+ > & {
1335
+ accountKeys: ParsedMessageAccount[];
1336
+ };
1337
+ }
1338
+ >;
915
1339
  };
916
1340
 
917
1341
  /**
918
- * Expected JSON RPC response for the "getBlockProduction" message
1342
+ * A processed block fetched from the RPC API where the `transactionDetails` mode is `none`
919
1343
  */
920
- const BlockProductionResponseStruct = jsonRpcResultAndContext(
921
- pick({
922
- byIdentity: record(string(), array(number())),
923
- range: pick({
924
- firstSlot: number(),
925
- lastSlot: number(),
926
- }),
1344
+ export type VersionedNoneModeBlockResponse = Omit<
1345
+ VersionedBlockResponse,
1346
+ 'transactions'
1347
+ >;
1348
+
1349
+ /**
1350
+ * A confirmed block on the ledger
1351
+ *
1352
+ * @deprecated Deprecated since Solana v1.8.0.
1353
+ */
1354
+ export type ConfirmedBlock = {
1355
+ /** Blockhash of this block */
1356
+ blockhash: Blockhash;
1357
+ /** Blockhash of this block's parent */
1358
+ previousBlockhash: Blockhash;
1359
+ /** Slot index of this block's parent */
1360
+ parentSlot: number;
1361
+ /** Vector of transactions and status metas */
1362
+ transactions: Array<{
1363
+ transaction: Transaction;
1364
+ meta: ConfirmedTransactionMeta | null;
1365
+ }>;
1366
+ /** Vector of block rewards */
1367
+ rewards?: Array<{
1368
+ pubkey: string;
1369
+ lamports: number;
1370
+ postBalance: number | null;
1371
+ rewardType: string | null;
1372
+ }>;
1373
+ /** The unix timestamp of when the block was processed */
1374
+ blockTime: number | null;
1375
+ };
1376
+
1377
+ /**
1378
+ * A Block on the ledger with signatures only
1379
+ */
1380
+ export type BlockSignatures = {
1381
+ /** Blockhash of this block */
1382
+ blockhash: Blockhash;
1383
+ /** Blockhash of this block's parent */
1384
+ previousBlockhash: Blockhash;
1385
+ /** Slot index of this block's parent */
1386
+ parentSlot: number;
1387
+ /** Vector of signatures */
1388
+ signatures: Array<string>;
1389
+ /** The unix timestamp of when the block was processed */
1390
+ blockTime: number | null;
1391
+ };
1392
+
1393
+ /**
1394
+ * recent block production information
1395
+ */
1396
+ export type BlockProduction = Readonly<{
1397
+ /** a dictionary of validator identities, as base-58 encoded strings. Value is a two element array containing the number of leader slots and the number of blocks produced */
1398
+ byIdentity: Readonly<Record<string, ReadonlyArray<number>>>;
1399
+ /** Block production slot range */
1400
+ range: Readonly<{
1401
+ /** first slot of the block production information (inclusive) */
1402
+ firstSlot: number;
1403
+ /** last slot of block production information (inclusive) */
1404
+ lastSlot: number;
1405
+ }>;
1406
+ }>;
1407
+
1408
+ export type GetBlockProductionConfig = {
1409
+ /** Optional commitment level */
1410
+ commitment?: Commitment;
1411
+ /** Slot range to return block production for. If parameter not provided, defaults to current epoch. */
1412
+ range?: {
1413
+ /** first slot to return block production information for (inclusive) */
1414
+ firstSlot: number;
1415
+ /** last slot to return block production information for (inclusive). If parameter not provided, defaults to the highest slot */
1416
+ lastSlot?: number;
1417
+ };
1418
+ /** Only return results for this validator identity (base-58 encoded) */
1419
+ identity?: string;
1420
+ };
1421
+
1422
+ /**
1423
+ * Expected JSON RPC response for the "getBlockProduction" message
1424
+ */
1425
+ const BlockProductionResponseStruct = jsonRpcResultAndContext(
1426
+ pick({
1427
+ byIdentity: record(string(), array(number())),
1428
+ range: pick({
1429
+ firstSlot: number(),
1430
+ lastSlot: number(),
1431
+ }),
927
1432
  }),
928
1433
  );
929
1434
 
@@ -943,29 +1448,64 @@ export type PerfSample = {
943
1448
 
944
1449
  function createRpcClient(
945
1450
  url: string,
946
- useHttps: boolean,
947
1451
  httpHeaders?: HttpHeaders,
948
- customFetch?: typeof crossFetch,
1452
+ customFetch?: FetchFn,
949
1453
  fetchMiddleware?: FetchMiddleware,
950
1454
  disableRetryOnRateLimit?: boolean,
1455
+ httpAgent?: HttpAgent | HttpsAgent | false,
951
1456
  ): RpcClient {
952
- const fetch = customFetch ? customFetch : crossFetch;
953
- let agentManager: AgentManager | undefined;
954
- if (!process.env.BROWSER) {
955
- agentManager = new AgentManager(useHttps);
1457
+ const fetch = customFetch ? customFetch : fetchImpl;
1458
+ let agent: HttpAgent | HttpsAgent | undefined;
1459
+ if (process.env.BROWSER) {
1460
+ if (httpAgent != null) {
1461
+ console.warn(
1462
+ 'You have supplied an `httpAgent` when creating a `Connection` in a browser environment.' +
1463
+ 'It has been ignored; `httpAgent` is only used in Node environments.',
1464
+ );
1465
+ }
1466
+ } else {
1467
+ if (httpAgent == null) {
1468
+ if (process.env.NODE_ENV !== 'test') {
1469
+ agent = new Agent({
1470
+ // One second fewer than the Solana RPC's keepalive timeout.
1471
+ // Read more: https://github.com/solana-labs/solana/issues/27859#issuecomment-1340097889
1472
+ freeSocketTimeout: 19000,
1473
+ keepAlive: true,
1474
+ maxSockets: 25,
1475
+ });
1476
+ }
1477
+ } else {
1478
+ if (httpAgent !== false) {
1479
+ const isHttps = url.startsWith('https:');
1480
+ if (isHttps && !(httpAgent instanceof HttpsAgent)) {
1481
+ throw new Error(
1482
+ 'The endpoint `' +
1483
+ url +
1484
+ '` can only be paired with an `https.Agent`. You have, instead, supplied an ' +
1485
+ '`http.Agent` through `httpAgent`.',
1486
+ );
1487
+ } else if (!isHttps && httpAgent instanceof HttpsAgent) {
1488
+ throw new Error(
1489
+ 'The endpoint `' +
1490
+ url +
1491
+ '` can only be paired with an `http.Agent`. You have, instead, supplied an ' +
1492
+ '`https.Agent` through `httpAgent`.',
1493
+ );
1494
+ }
1495
+ agent = httpAgent;
1496
+ }
1497
+ }
956
1498
  }
957
1499
 
958
- let fetchWithMiddleware:
959
- | ((url: string, options: any) => Promise<Response>)
960
- | undefined;
1500
+ let fetchWithMiddleware: FetchFn | undefined;
961
1501
 
962
1502
  if (fetchMiddleware) {
963
- fetchWithMiddleware = async (url: string, options: any) => {
964
- const modifiedFetchArgs = await new Promise<[string, any]>(
1503
+ fetchWithMiddleware = async (info, init) => {
1504
+ const modifiedFetchArgs = await new Promise<Parameters<FetchFn>>(
965
1505
  (resolve, reject) => {
966
1506
  try {
967
- fetchMiddleware(url, options, (modifiedUrl, modifiedOptions) =>
968
- resolve([modifiedUrl, modifiedOptions]),
1507
+ fetchMiddleware(info, init, (modifiedInfo, modifiedInit) =>
1508
+ resolve([modifiedInfo, modifiedInit]),
969
1509
  );
970
1510
  } catch (error) {
971
1511
  reject(error);
@@ -977,7 +1517,6 @@ function createRpcClient(
977
1517
  }
978
1518
 
979
1519
  const clientBrowser = new RpcClient(async (request, callback) => {
980
- const agent = agentManager ? agentManager.requestStart() : undefined;
981
1520
  const options = {
982
1521
  method: 'POST',
983
1522
  body: request,
@@ -987,6 +1526,7 @@ function createRpcClient(
987
1526
  'Content-Type': 'application/json',
988
1527
  },
989
1528
  httpHeaders || {},
1529
+ COMMON_HTTP_HEADERS,
990
1530
  ),
991
1531
  };
992
1532
 
@@ -1026,8 +1566,6 @@ function createRpcClient(
1026
1566
  }
1027
1567
  } catch (err) {
1028
1568
  if (err instanceof Error) callback(err);
1029
- } finally {
1030
- agentManager && agentManager.requestEnd();
1031
1569
  }
1032
1570
  }, {});
1033
1571
 
@@ -1549,6 +2087,12 @@ const GetSignatureStatusesRpcResult = jsonRpcResultAndContext(
1549
2087
  */
1550
2088
  const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult(number());
1551
2089
 
2090
+ const AddressTableLookupStruct = pick({
2091
+ accountKey: PublicKeyFromString,
2092
+ writableIndexes: array(number()),
2093
+ readonlyIndexes: array(number()),
2094
+ });
2095
+
1552
2096
  const ConfirmedTransactionResult = pick({
1553
2097
  signatures: array(string()),
1554
2098
  message: pick({
@@ -1566,9 +2110,22 @@ const ConfirmedTransactionResult = pick({
1566
2110
  }),
1567
2111
  ),
1568
2112
  recentBlockhash: string(),
2113
+ addressTableLookups: optional(array(AddressTableLookupStruct)),
1569
2114
  }),
1570
2115
  });
1571
2116
 
2117
+ const AnnotatedAccountKey = pick({
2118
+ pubkey: PublicKeyFromString,
2119
+ signer: boolean(),
2120
+ writable: boolean(),
2121
+ source: optional(union([literal('transaction'), literal('lookupTable')])),
2122
+ });
2123
+
2124
+ const ConfirmedTransactionAccountsModeResult = pick({
2125
+ accountKeys: array(AnnotatedAccountKey),
2126
+ signatures: array(string()),
2127
+ });
2128
+
1572
2129
  const ParsedInstructionResult = pick({
1573
2130
  parsed: unknown(),
1574
2131
  program: string(),
@@ -1617,15 +2174,10 @@ const ParsedOrRawInstruction = coerce(
1617
2174
  const ParsedConfirmedTransactionResult = pick({
1618
2175
  signatures: array(string()),
1619
2176
  message: pick({
1620
- accountKeys: array(
1621
- pick({
1622
- pubkey: PublicKeyFromString,
1623
- signer: boolean(),
1624
- writable: boolean(),
1625
- }),
1626
- ),
2177
+ accountKeys: array(AnnotatedAccountKey),
1627
2178
  instructions: array(ParsedOrRawInstruction),
1628
2179
  recentBlockhash: string(),
2180
+ addressTableLookups: optional(nullable(array(AddressTableLookupStruct))),
1629
2181
  }),
1630
2182
  });
1631
2183
 
@@ -1636,6 +2188,11 @@ const TokenBalanceResult = pick({
1636
2188
  uiTokenAmount: TokenAmountResult,
1637
2189
  });
1638
2190
 
2191
+ const LoadedAddressesResult = pick({
2192
+ writable: array(PublicKeyFromString),
2193
+ readonly: array(PublicKeyFromString),
2194
+ });
2195
+
1639
2196
  /**
1640
2197
  * @internal
1641
2198
  */
@@ -1663,6 +2220,8 @@ const ConfirmedTransactionMetaResult = pick({
1663
2220
  logMessages: optional(nullable(array(string()))),
1664
2221
  preTokenBalances: optional(nullable(array(TokenBalanceResult))),
1665
2222
  postTokenBalances: optional(nullable(array(TokenBalanceResult))),
2223
+ loadedAddresses: optional(LoadedAddressesResult),
2224
+ computeUnitsConsumed: optional(number()),
1666
2225
  });
1667
2226
 
1668
2227
  /**
@@ -1686,6 +2245,18 @@ const ParsedConfirmedTransactionMetaResult = pick({
1686
2245
  logMessages: optional(nullable(array(string()))),
1687
2246
  preTokenBalances: optional(nullable(array(TokenBalanceResult))),
1688
2247
  postTokenBalances: optional(nullable(array(TokenBalanceResult))),
2248
+ loadedAddresses: optional(LoadedAddressesResult),
2249
+ computeUnitsConsumed: optional(number()),
2250
+ });
2251
+
2252
+ const TransactionVersionStruct = union([literal(0), literal('legacy')]);
2253
+
2254
+ /** @internal */
2255
+ const RewardsResult = pick({
2256
+ pubkey: string(),
2257
+ lamports: number(),
2258
+ postBalance: nullable(number()),
2259
+ rewardType: nullable(string()),
1689
2260
  });
1690
2261
 
1691
2262
  /**
@@ -1701,18 +2272,111 @@ const GetBlockRpcResult = jsonRpcResult(
1701
2272
  pick({
1702
2273
  transaction: ConfirmedTransactionResult,
1703
2274
  meta: nullable(ConfirmedTransactionMetaResult),
2275
+ version: optional(TransactionVersionStruct),
1704
2276
  }),
1705
2277
  ),
1706
- rewards: optional(
1707
- array(
1708
- pick({
1709
- pubkey: string(),
1710
- lamports: number(),
1711
- postBalance: nullable(number()),
1712
- rewardType: nullable(string()),
1713
- }),
1714
- ),
2278
+ rewards: optional(array(RewardsResult)),
2279
+ blockTime: nullable(number()),
2280
+ blockHeight: nullable(number()),
2281
+ }),
2282
+ ),
2283
+ );
2284
+
2285
+ /**
2286
+ * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `none`
2287
+ */
2288
+ const GetNoneModeBlockRpcResult = jsonRpcResult(
2289
+ nullable(
2290
+ pick({
2291
+ blockhash: string(),
2292
+ previousBlockhash: string(),
2293
+ parentSlot: number(),
2294
+ rewards: optional(array(RewardsResult)),
2295
+ blockTime: nullable(number()),
2296
+ blockHeight: nullable(number()),
2297
+ }),
2298
+ ),
2299
+ );
2300
+
2301
+ /**
2302
+ * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts`
2303
+ */
2304
+ const GetAccountsModeBlockRpcResult = jsonRpcResult(
2305
+ nullable(
2306
+ pick({
2307
+ blockhash: string(),
2308
+ previousBlockhash: string(),
2309
+ parentSlot: number(),
2310
+ transactions: array(
2311
+ pick({
2312
+ transaction: ConfirmedTransactionAccountsModeResult,
2313
+ meta: nullable(ConfirmedTransactionMetaResult),
2314
+ version: optional(TransactionVersionStruct),
2315
+ }),
2316
+ ),
2317
+ rewards: optional(array(RewardsResult)),
2318
+ blockTime: nullable(number()),
2319
+ blockHeight: nullable(number()),
2320
+ }),
2321
+ ),
2322
+ );
2323
+
2324
+ /**
2325
+ * Expected parsed JSON RPC response for the "getBlock" message
2326
+ */
2327
+ const GetParsedBlockRpcResult = jsonRpcResult(
2328
+ nullable(
2329
+ pick({
2330
+ blockhash: string(),
2331
+ previousBlockhash: string(),
2332
+ parentSlot: number(),
2333
+ transactions: array(
2334
+ pick({
2335
+ transaction: ParsedConfirmedTransactionResult,
2336
+ meta: nullable(ParsedConfirmedTransactionMetaResult),
2337
+ version: optional(TransactionVersionStruct),
2338
+ }),
2339
+ ),
2340
+ rewards: optional(array(RewardsResult)),
2341
+ blockTime: nullable(number()),
2342
+ blockHeight: nullable(number()),
2343
+ }),
2344
+ ),
2345
+ );
2346
+
2347
+ /**
2348
+ * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts`
2349
+ */
2350
+ const GetParsedAccountsModeBlockRpcResult = jsonRpcResult(
2351
+ nullable(
2352
+ pick({
2353
+ blockhash: string(),
2354
+ previousBlockhash: string(),
2355
+ parentSlot: number(),
2356
+ transactions: array(
2357
+ pick({
2358
+ transaction: ConfirmedTransactionAccountsModeResult,
2359
+ meta: nullable(ParsedConfirmedTransactionMetaResult),
2360
+ version: optional(TransactionVersionStruct),
2361
+ }),
1715
2362
  ),
2363
+ rewards: optional(array(RewardsResult)),
2364
+ blockTime: nullable(number()),
2365
+ blockHeight: nullable(number()),
2366
+ }),
2367
+ ),
2368
+ );
2369
+
2370
+ /**
2371
+ * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `none`
2372
+ */
2373
+ const GetParsedNoneModeBlockRpcResult = jsonRpcResult(
2374
+ nullable(
2375
+ pick({
2376
+ blockhash: string(),
2377
+ previousBlockhash: string(),
2378
+ parentSlot: number(),
2379
+ rewards: optional(array(RewardsResult)),
1716
2380
  blockTime: nullable(number()),
1717
2381
  blockHeight: nullable(number()),
1718
2382
  }),
@@ -1736,16 +2400,7 @@ const GetConfirmedBlockRpcResult = jsonRpcResult(
1736
2400
  meta: nullable(ConfirmedTransactionMetaResult),
1737
2401
  }),
1738
2402
  ),
1739
- rewards: optional(
1740
- array(
1741
- pick({
1742
- pubkey: string(),
1743
- lamports: number(),
1744
- postBalance: nullable(number()),
1745
- rewardType: nullable(string()),
1746
- }),
1747
- ),
1748
- ),
2403
+ rewards: optional(array(RewardsResult)),
1749
2404
  blockTime: nullable(number()),
1750
2405
  }),
1751
2406
  ),
@@ -1776,6 +2431,7 @@ const GetTransactionRpcResult = jsonRpcResult(
1776
2431
  meta: ConfirmedTransactionMetaResult,
1777
2432
  blockTime: optional(nullable(number())),
1778
2433
  transaction: ConfirmedTransactionResult,
2434
+ version: optional(TransactionVersionStruct),
1779
2435
  }),
1780
2436
  ),
1781
2437
  );
@@ -1790,6 +2446,7 @@ const GetParsedTransactionRpcResult = jsonRpcResult(
1790
2446
  transaction: ParsedConfirmedTransactionResult,
1791
2447
  meta: nullable(ParsedConfirmedTransactionMetaResult),
1792
2448
  blockTime: optional(nullable(number())),
2449
+ version: optional(TransactionVersionStruct),
1793
2450
  }),
1794
2451
  ),
1795
2452
  );
@@ -1940,6 +2597,8 @@ export type GetProgramAccountsConfig = {
1940
2597
  dataSlice?: DataSlice;
1941
2598
  /** Optional array of filters to apply to accounts */
1942
2599
  filters?: GetProgramAccountsFilter[];
2600
+ /** The minimum slot that the request can be evaluated at */
2601
+ minContextSlot?: number;
1943
2602
  };
1944
2603
 
1945
2604
  /**
@@ -1950,6 +2609,8 @@ export type GetParsedProgramAccountsConfig = {
1950
2609
  commitment?: Commitment;
1951
2610
  /** Optional array of filters to apply to accounts */
1952
2611
  filters?: GetProgramAccountsFilter[];
2612
+ /** The minimum slot that the request can be evaluated at */
2613
+ minContextSlot?: number;
1953
2614
  };
1954
2615
 
1955
2616
  /**
@@ -1958,8 +2619,62 @@ export type GetParsedProgramAccountsConfig = {
1958
2619
  export type GetMultipleAccountsConfig = {
1959
2620
  /** Optional commitment level */
1960
2621
  commitment?: Commitment;
1961
- /** Optional encoding for account data (default base64) */
1962
- encoding?: 'base64' | 'jsonParsed';
2622
+ /** The minimum slot that the request can be evaluated at */
2623
+ minContextSlot?: number;
2624
+ /** Optional data slice to limit the returned account data */
2625
+ dataSlice?: DataSlice;
2626
+ };
2627
+
2628
+ /**
2629
+ * Configuration object for `getStakeActivation`
2630
+ */
2631
+ export type GetStakeActivationConfig = {
2632
+ /** Optional commitment level */
2633
+ commitment?: Commitment;
2634
+ /** Epoch for which to calculate activation details. If parameter not provided, defaults to current epoch */
2635
+ epoch?: number;
2636
+ /** The minimum slot that the request can be evaluated at */
2637
+ minContextSlot?: number;
2638
+ };
2639
+
2640
+ /**
2641
+ * Configuration object for `getStakeActivation`
2642
+ */
2643
+ export type GetTokenAccountsByOwnerConfig = {
2644
+ /** Optional commitment level */
2645
+ commitment?: Commitment;
2646
+ /** The minimum slot that the request can be evaluated at */
2647
+ minContextSlot?: number;
2648
+ };
2649
+
2650
+ /**
2651
+ * Configuration object for `getStakeActivation`
2652
+ */
2653
+ export type GetTransactionCountConfig = {
2654
+ /** Optional commitment level */
2655
+ commitment?: Commitment;
2656
+ /** The minimum slot that the request can be evaluated at */
2657
+ minContextSlot?: number;
2658
+ };
2659
+
2660
+ /**
2661
+ * Configuration object for `getNonce`
2662
+ */
2663
+ export type GetNonceConfig = {
2664
+ /** Optional commitment level */
2665
+ commitment?: Commitment;
2666
+ /** The minimum slot that the request can be evaluated at */
2667
+ minContextSlot?: number;
2668
+ };
2669
+
2670
+ /**
2671
+ * Configuration object for `getNonceAndContext`
2672
+ */
2673
+ export type GetNonceAndContextConfig = {
2674
+ /** Optional commitment level */
2675
+ commitment?: Commitment;
2676
+ /** The minimum slot that the request can be evaluated at */
2677
+ minContextSlot?: number;
1963
2678
  };
1964
2679
 
1965
2680
  /**
@@ -2047,7 +2762,7 @@ export type SignatureSubscriptionCallback = (
2047
2762
  * Signature subscription options
2048
2763
  */
2049
2764
  export type SignatureSubscriptionOptions = {
2050
- commitment?: Commitment; // Must default to 'finalized'
2765
+ commitment?: Commitment;
2051
2766
  enableReceivedNotification?: boolean;
2052
2767
  };
2053
2768
 
@@ -2145,26 +2860,44 @@ export type ConfirmedSignatureInfo = {
2145
2860
  memo: string | null;
2146
2861
  /** The unix timestamp of when the transaction was processed */
2147
2862
  blockTime?: number | null;
2863
+ /** Cluster confirmation status, if available. Possible values: `processed`, `confirmed`, `finalized` */
2864
+ confirmationStatus?: TransactionConfirmationStatus;
2148
2865
  };
2149
2866
 
2150
2867
  /**
2151
2868
  * An object defining headers to be passed to the RPC server
2152
2869
  */
2153
- export type HttpHeaders = {[header: string]: string};
2870
+ export type HttpHeaders = {
2871
+ [header: string]: string;
2872
+ } & {
2873
+ // Prohibited headers; for internal use only.
2874
+ 'solana-client'?: never;
2875
+ };
2876
+
2877
+ /**
2878
+ * The type of the JavaScript `fetch()` API
2879
+ */
2880
+ export type FetchFn = typeof fetchImpl;
2154
2881
 
2155
2882
  /**
2156
2883
  * A callback used to augment the outgoing HTTP request
2157
2884
  */
2158
2885
  export type FetchMiddleware = (
2159
- url: string,
2160
- options: any,
2161
- fetch: (modifiedUrl: string, modifiedOptions: any) => void,
2886
+ info: Parameters<FetchFn>[0],
2887
+ init: Parameters<FetchFn>[1],
2888
+ fetch: (...a: Parameters<FetchFn>) => void,
2162
2889
  ) => void;
2163
2890
 
2164
2891
  /**
2165
2892
  * Configuration for instantiating a Connection
2166
2893
  */
2167
2894
  export type ConnectionConfig = {
2895
+ /**
2896
+ * An `http.Agent` that will be used to manage socket connections (eg. to implement connection
2897
+ * persistence). Set this to `false` to create a connection that uses no agent. This applies to
2898
+ * Node environments only.
2899
+ */
2900
+ httpAgent?: HttpAgent | HttpsAgent | false;
2168
2901
  /** Optional commitment level */
2169
2902
  commitment?: Commitment;
2170
2903
  /** Optional endpoint URL to the fullnode JSON RPC PubSub WebSocket Endpoint */
@@ -2172,7 +2905,7 @@ export type ConnectionConfig = {
2172
2905
  /** Optional HTTP headers object */
2173
2906
  httpHeaders?: HttpHeaders;
2174
2907
  /** Optional custom fetch function */
2175
- fetch?: typeof crossFetch;
2908
+ fetch?: FetchFn;
2176
2909
  /** Optional fetch middleware callback */
2177
2910
  fetchMiddleware?: FetchMiddleware;
2178
2911
  /** Optional Disable retrying calls when server responds with HTTP 429 (Too Many Requests) */
@@ -2181,6 +2914,11 @@ export type ConnectionConfig = {
2181
2914
  confirmTransactionInitialTimeout?: number;
2182
2915
  };
2183
2916
 
2917
+ /** @internal */
2918
+ const COMMON_HTTP_HEADERS = {
2919
+ 'solana-client': `js/${process.env.npm_package_version ?? 'UNKNOWN'}`,
2920
+ };
2921
+
2184
2922
  /**
2185
2923
  * A connection to a fullnode JSON RPC endpoint
2186
2924
  */
@@ -2200,33 +2938,50 @@ export class Connection {
2200
2938
  /** @internal */ _rpcWebSocketIdleTimeout: ReturnType<
2201
2939
  typeof setTimeout
2202
2940
  > | null = null;
2941
+ /** @internal
2942
+ * A number that we increment every time an active connection closes.
2943
+ * Used to determine whether the same socket connection that was open
2944
+ * when an async operation started is the same one that's active when
2945
+ * its continuation fires.
2946
+ *
2947
+ */ private _rpcWebSocketGeneration: number = 0;
2203
2948
 
2204
2949
  /** @internal */ _disableBlockhashCaching: boolean = false;
2205
2950
  /** @internal */ _pollingBlockhash: boolean = false;
2206
2951
  /** @internal */ _blockhashInfo: {
2207
- recentBlockhash: Blockhash | null;
2952
+ latestBlockhash: BlockhashWithExpiryBlockHeight | null;
2208
2953
  lastFetch: number;
2209
2954
  simulatedSignatures: Array<string>;
2210
2955
  transactionSignatures: Array<string>;
2211
2956
  } = {
2212
- recentBlockhash: null,
2957
+ latestBlockhash: null,
2213
2958
  lastFetch: 0,
2214
2959
  transactionSignatures: [],
2215
2960
  simulatedSignatures: [],
2216
2961
  };
2217
2962
 
2218
- /** @internal */ _nextClientSubscriptionId: ClientSubscriptionId = 0;
2219
- /** @internal */ _subscriptionDisposeFunctionsByClientSubscriptionId: {
2963
+ /** @internal */ private _nextClientSubscriptionId: ClientSubscriptionId = 0;
2964
+ /** @internal */ private _subscriptionDisposeFunctionsByClientSubscriptionId: {
2220
2965
  [clientSubscriptionId: ClientSubscriptionId]:
2221
2966
  | SubscriptionDisposeFn
2222
2967
  | undefined;
2223
2968
  } = {};
2224
- /** @internal */ _subscriptionCallbacksByServerSubscriptionId: {
2969
+ /** @internal */ private _subscriptionHashByClientSubscriptionId: {
2970
+ [clientSubscriptionId: ClientSubscriptionId]:
2971
+ | SubscriptionConfigHash
2972
+ | undefined;
2973
+ } = {};
2974
+ /** @internal */ private _subscriptionStateChangeCallbacksByHash: {
2975
+ [hash: SubscriptionConfigHash]:
2976
+ | Set<SubscriptionStateChangeCallback>
2977
+ | undefined;
2978
+ } = {};
2979
+ /** @internal */ private _subscriptionCallbacksByServerSubscriptionId: {
2225
2980
  [serverSubscriptionId: ServerSubscriptionId]:
2226
2981
  | Set<SubscriptionConfig['callback']>
2227
2982
  | undefined;
2228
2983
  } = {};
2229
- /** @internal */ _subscriptionsByHash: {
2984
+ /** @internal */ private _subscriptionsByHash: {
2230
2985
  [hash: SubscriptionConfigHash]: Subscription | undefined;
2231
2986
  } = {};
2232
2987
  /**
@@ -2242,7 +2997,7 @@ export class Connection {
2242
2997
  * NOTE: There is a proposal to eliminate this special case, here:
2243
2998
  * https://github.com/solana-labs/solana/issues/18892
2244
2999
  */
2245
- /** @internal */ _subscriptionsAutoDisposedByRpc: Set<ServerSubscriptionId> =
3000
+ /** @internal */ private _subscriptionsAutoDisposedByRpc: Set<ServerSubscriptionId> =
2246
3001
  new Set();
2247
3002
 
2248
3003
  /**
@@ -2255,14 +3010,12 @@ export class Connection {
2255
3010
  endpoint: string,
2256
3011
  commitmentOrConfig?: Commitment | ConnectionConfig,
2257
3012
  ) {
2258
- let url = new URL(endpoint);
2259
- const useHttps = url.protocol === 'https:';
2260
-
2261
3013
  let wsEndpoint;
2262
3014
  let httpHeaders;
2263
3015
  let fetch;
2264
3016
  let fetchMiddleware;
2265
3017
  let disableRetryOnRateLimit;
3018
+ let httpAgent;
2266
3019
  if (commitmentOrConfig && typeof commitmentOrConfig === 'string') {
2267
3020
  this._commitment = commitmentOrConfig;
2268
3021
  } else if (commitmentOrConfig) {
@@ -2274,18 +3027,19 @@ export class Connection {
2274
3027
  fetch = commitmentOrConfig.fetch;
2275
3028
  fetchMiddleware = commitmentOrConfig.fetchMiddleware;
2276
3029
  disableRetryOnRateLimit = commitmentOrConfig.disableRetryOnRateLimit;
3030
+ httpAgent = commitmentOrConfig.httpAgent;
2277
3031
  }
2278
3032
 
2279
- this._rpcEndpoint = endpoint;
3033
+ this._rpcEndpoint = assertEndpointUrl(endpoint);
2280
3034
  this._rpcWsEndpoint = wsEndpoint || makeWebsocketUrl(endpoint);
2281
3035
 
2282
3036
  this._rpcClient = createRpcClient(
2283
- url.toString(),
2284
- useHttps,
3037
+ endpoint,
2285
3038
  httpHeaders,
2286
3039
  fetch,
2287
3040
  fetchMiddleware,
2288
3041
  disableRetryOnRateLimit,
3042
+ httpAgent,
2289
3043
  );
2290
3044
  this._rpcRequest = createRpcRequest(this._rpcClient);
2291
3045
  this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient);
@@ -2346,17 +3100,23 @@ export class Connection {
2346
3100
  */
2347
3101
  async getBalanceAndContext(
2348
3102
  publicKey: PublicKey,
2349
- commitment?: Commitment,
3103
+ commitmentOrConfig?: Commitment | GetBalanceConfig,
2350
3104
  ): Promise<RpcResponseAndContext<number>> {
2351
- const args = this._buildArgs([publicKey.toBase58()], commitment);
3105
+ /** @internal */
3106
+ const {commitment, config} =
3107
+ extractCommitmentFromConfig(commitmentOrConfig);
3108
+ const args = this._buildArgs(
3109
+ [publicKey.toBase58()],
3110
+ commitment,
3111
+ undefined /* encoding */,
3112
+ config,
3113
+ );
2352
3114
  const unsafeRes = await this._rpcRequest('getBalance', args);
2353
3115
  const res = create(unsafeRes, jsonRpcResultAndContext(number()));
2354
3116
  if ('error' in res) {
2355
- throw new Error(
2356
- 'failed to get balance for ' +
2357
- publicKey.toBase58() +
2358
- ': ' +
2359
- res.error.message,
3117
+ throw new SolanaJSONRPCError(
3118
+ res.error,
3119
+ `failed to get balance for ${publicKey.toBase58()}`,
2360
3120
  );
2361
3121
  }
2362
3122
  return res.result;
@@ -2367,9 +3127,9 @@ export class Connection {
2367
3127
  */
2368
3128
  async getBalance(
2369
3129
  publicKey: PublicKey,
2370
- commitment?: Commitment,
3130
+ commitmentOrConfig?: Commitment | GetBalanceConfig,
2371
3131
  ): Promise<number> {
2372
- return await this.getBalanceAndContext(publicKey, commitment)
3132
+ return await this.getBalanceAndContext(publicKey, commitmentOrConfig)
2373
3133
  .then(x => x.value)
2374
3134
  .catch(e => {
2375
3135
  throw new Error(
@@ -2385,8 +3145,9 @@ export class Connection {
2385
3145
  const unsafeRes = await this._rpcRequest('getBlockTime', [slot]);
2386
3146
  const res = create(unsafeRes, jsonRpcResult(nullable(number())));
2387
3147
  if ('error' in res) {
2388
- throw new Error(
2389
- 'failed to get block time for slot ' + slot + ': ' + res.error.message,
3148
+ throw new SolanaJSONRPCError(
3149
+ res.error,
3150
+ `failed to get block time for slot ${slot}`,
2390
3151
  );
2391
3152
  }
2392
3153
  return res.result;
@@ -2400,8 +3161,9 @@ export class Connection {
2400
3161
  const unsafeRes = await this._rpcRequest('minimumLedgerSlot', []);
2401
3162
  const res = create(unsafeRes, jsonRpcResult(number()));
2402
3163
  if ('error' in res) {
2403
- throw new Error(
2404
- 'failed to get minimum ledger slot: ' + res.error.message,
3164
+ throw new SolanaJSONRPCError(
3165
+ res.error,
3166
+ 'failed to get minimum ledger slot',
2405
3167
  );
2406
3168
  }
2407
3169
  return res.result;
@@ -2414,8 +3176,9 @@ export class Connection {
2414
3176
  const unsafeRes = await this._rpcRequest('getFirstAvailableBlock', []);
2415
3177
  const res = create(unsafeRes, SlotRpcResult);
2416
3178
  if ('error' in res) {
2417
- throw new Error(
2418
- 'failed to get first available block: ' + res.error.message,
3179
+ throw new SolanaJSONRPCError(
3180
+ res.error,
3181
+ 'failed to get first available block',
2419
3182
  );
2420
3183
  }
2421
3184
  return res.result;
@@ -2444,7 +3207,7 @@ export class Connection {
2444
3207
  const unsafeRes = await this._rpcRequest('getSupply', [configArg]);
2445
3208
  const res = create(unsafeRes, GetSupplyRpcResult);
2446
3209
  if ('error' in res) {
2447
- throw new Error('failed to get supply: ' + res.error.message);
3210
+ throw new SolanaJSONRPCError(res.error, 'failed to get supply');
2448
3211
  }
2449
3212
  return res.result;
2450
3213
  }
@@ -2460,7 +3223,7 @@ export class Connection {
2460
3223
  const unsafeRes = await this._rpcRequest('getTokenSupply', args);
2461
3224
  const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult));
2462
3225
  if ('error' in res) {
2463
- throw new Error('failed to get token supply: ' + res.error.message);
3226
+ throw new SolanaJSONRPCError(res.error, 'failed to get token supply');
2464
3227
  }
2465
3228
  return res.result;
2466
3229
  }
@@ -2476,8 +3239,9 @@ export class Connection {
2476
3239
  const unsafeRes = await this._rpcRequest('getTokenAccountBalance', args);
2477
3240
  const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult));
2478
3241
  if ('error' in res) {
2479
- throw new Error(
2480
- 'failed to get token account balance: ' + res.error.message,
3242
+ throw new SolanaJSONRPCError(
3243
+ res.error,
3244
+ 'failed to get token account balance',
2481
3245
  );
2482
3246
  }
2483
3247
  return res.result;
@@ -2491,12 +3255,14 @@ export class Connection {
2491
3255
  async getTokenAccountsByOwner(
2492
3256
  ownerAddress: PublicKey,
2493
3257
  filter: TokenAccountsFilter,
2494
- commitment?: Commitment,
3258
+ commitmentOrConfig?: Commitment | GetTokenAccountsByOwnerConfig,
2495
3259
  ): Promise<
2496
3260
  RpcResponseAndContext<
2497
3261
  Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>
2498
3262
  >
2499
3263
  > {
3264
+ const {commitment, config} =
3265
+ extractCommitmentFromConfig(commitmentOrConfig);
2500
3266
  let _args: any[] = [ownerAddress.toBase58()];
2501
3267
  if ('mint' in filter) {
2502
3268
  _args.push({mint: filter.mint.toBase58()});
@@ -2504,15 +3270,13 @@ export class Connection {
2504
3270
  _args.push({programId: filter.programId.toBase58()});
2505
3271
  }
2506
3272
 
2507
- const args = this._buildArgs(_args, commitment, 'base64');
3273
+ const args = this._buildArgs(_args, commitment, 'base64', config);
2508
3274
  const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args);
2509
3275
  const res = create(unsafeRes, GetTokenAccountsByOwner);
2510
3276
  if ('error' in res) {
2511
- throw new Error(
2512
- 'failed to get token accounts owned by account ' +
2513
- ownerAddress.toBase58() +
2514
- ': ' +
2515
- res.error.message,
3277
+ throw new SolanaJSONRPCError(
3278
+ res.error,
3279
+ `failed to get token accounts owned by account ${ownerAddress.toBase58()}`,
2516
3280
  );
2517
3281
  }
2518
3282
  return res.result;
@@ -2543,11 +3307,9 @@ export class Connection {
2543
3307
  const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args);
2544
3308
  const res = create(unsafeRes, GetParsedTokenAccountsByOwner);
2545
3309
  if ('error' in res) {
2546
- throw new Error(
2547
- 'failed to get token accounts owned by account ' +
2548
- ownerAddress.toBase58() +
2549
- ': ' +
2550
- res.error.message,
3310
+ throw new SolanaJSONRPCError(
3311
+ res.error,
3312
+ `failed to get token accounts owned by account ${ownerAddress.toBase58()}`,
2551
3313
  );
2552
3314
  }
2553
3315
  return res.result;
@@ -2567,7 +3329,7 @@ export class Connection {
2567
3329
  const unsafeRes = await this._rpcRequest('getLargestAccounts', args);
2568
3330
  const res = create(unsafeRes, GetLargestAccountsRpcResult);
2569
3331
  if ('error' in res) {
2570
- throw new Error('failed to get largest accounts: ' + res.error.message);
3332
+ throw new SolanaJSONRPCError(res.error, 'failed to get largest accounts');
2571
3333
  }
2572
3334
  return res.result;
2573
3335
  }
@@ -2584,8 +3346,9 @@ export class Connection {
2584
3346
  const unsafeRes = await this._rpcRequest('getTokenLargestAccounts', args);
2585
3347
  const res = create(unsafeRes, GetTokenLargestAccountsResult);
2586
3348
  if ('error' in res) {
2587
- throw new Error(
2588
- 'failed to get token largest accounts: ' + res.error.message,
3349
+ throw new SolanaJSONRPCError(
3350
+ res.error,
3351
+ 'failed to get token largest accounts',
2589
3352
  );
2590
3353
  }
2591
3354
  return res.result;
@@ -2596,20 +3359,25 @@ export class Connection {
2596
3359
  */
2597
3360
  async getAccountInfoAndContext(
2598
3361
  publicKey: PublicKey,
2599
- commitment?: Commitment,
3362
+ commitmentOrConfig?: Commitment | GetAccountInfoConfig,
2600
3363
  ): Promise<RpcResponseAndContext<AccountInfo<Buffer> | null>> {
2601
- const args = this._buildArgs([publicKey.toBase58()], commitment, 'base64');
3364
+ const {commitment, config} =
3365
+ extractCommitmentFromConfig(commitmentOrConfig);
3366
+ const args = this._buildArgs(
3367
+ [publicKey.toBase58()],
3368
+ commitment,
3369
+ 'base64',
3370
+ config,
3371
+ );
2602
3372
  const unsafeRes = await this._rpcRequest('getAccountInfo', args);
2603
3373
  const res = create(
2604
3374
  unsafeRes,
2605
3375
  jsonRpcResultAndContext(nullable(AccountInfoResult)),
2606
3376
  );
2607
3377
  if ('error' in res) {
2608
- throw new Error(
2609
- 'failed to get info about account ' +
2610
- publicKey.toBase58() +
2611
- ': ' +
2612
- res.error.message,
3378
+ throw new SolanaJSONRPCError(
3379
+ res.error,
3380
+ `failed to get info about account ${publicKey.toBase58()}`,
2613
3381
  );
2614
3382
  }
2615
3383
  return res.result;
@@ -2620,14 +3388,17 @@ export class Connection {
2620
3388
  */
2621
3389
  async getParsedAccountInfo(
2622
3390
  publicKey: PublicKey,
2623
- commitment?: Commitment,
3391
+ commitmentOrConfig?: Commitment | GetAccountInfoConfig,
2624
3392
  ): Promise<
2625
3393
  RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>
2626
3394
  > {
3395
+ const {commitment, config} =
3396
+ extractCommitmentFromConfig(commitmentOrConfig);
2627
3397
  const args = this._buildArgs(
2628
3398
  [publicKey.toBase58()],
2629
3399
  commitment,
2630
3400
  'jsonParsed',
3401
+ config,
2631
3402
  );
2632
3403
  const unsafeRes = await this._rpcRequest('getAccountInfo', args);
2633
3404
  const res = create(
@@ -2635,11 +3406,9 @@ export class Connection {
2635
3406
  jsonRpcResultAndContext(nullable(ParsedAccountInfoResult)),
2636
3407
  );
2637
3408
  if ('error' in res) {
2638
- throw new Error(
2639
- 'failed to get info about account ' +
2640
- publicKey.toBase58() +
2641
- ': ' +
2642
- res.error.message,
3409
+ throw new SolanaJSONRPCError(
3410
+ res.error,
3411
+ `failed to get info about account ${publicKey.toBase58()}`,
2643
3412
  );
2644
3413
  }
2645
3414
  return res.result;
@@ -2650,10 +3419,13 @@ export class Connection {
2650
3419
  */
2651
3420
  async getAccountInfo(
2652
3421
  publicKey: PublicKey,
2653
- commitment?: Commitment,
3422
+ commitmentOrConfig?: Commitment | GetAccountInfoConfig,
2654
3423
  ): Promise<AccountInfo<Buffer> | null> {
2655
3424
  try {
2656
- const res = await this.getAccountInfoAndContext(publicKey, commitment);
3425
+ const res = await this.getAccountInfoAndContext(
3426
+ publicKey,
3427
+ commitmentOrConfig,
3428
+ );
2657
3429
  return res.value;
2658
3430
  } catch (e) {
2659
3431
  throw new Error(
@@ -2662,23 +3434,52 @@ export class Connection {
2662
3434
  }
2663
3435
  }
2664
3436
 
3437
+ /**
3438
+ * Fetch all the account info for multiple accounts specified by an array of public keys, return with context
3439
+ */
3440
+ async getMultipleParsedAccounts(
3441
+ publicKeys: PublicKey[],
3442
+ rawConfig?: GetMultipleAccountsConfig,
3443
+ ): Promise<
3444
+ RpcResponseAndContext<(AccountInfo<Buffer | ParsedAccountData> | null)[]>
3445
+ > {
3446
+ const {commitment, config} = extractCommitmentFromConfig(rawConfig);
3447
+ const keys = publicKeys.map(key => key.toBase58());
3448
+ const args = this._buildArgs([keys], commitment, 'jsonParsed', config);
3449
+ const unsafeRes = await this._rpcRequest('getMultipleAccounts', args);
3450
+ const res = create(
3451
+ unsafeRes,
3452
+ jsonRpcResultAndContext(array(nullable(ParsedAccountInfoResult))),
3453
+ );
3454
+ if ('error' in res) {
3455
+ throw new SolanaJSONRPCError(
3456
+ res.error,
3457
+ `failed to get info for accounts ${keys}`,
3458
+ );
3459
+ }
3460
+ return res.result;
3461
+ }
3462
+
2665
3463
  /**
2666
3464
  * Fetch all the account info for multiple accounts specified by an array of public keys, return with context
2667
3465
  */
2668
3466
  async getMultipleAccountsInfoAndContext(
2669
3467
  publicKeys: PublicKey[],
2670
- commitment?: Commitment,
3468
+ commitmentOrConfig?: Commitment | GetMultipleAccountsConfig,
2671
3469
  ): Promise<RpcResponseAndContext<(AccountInfo<Buffer> | null)[]>> {
3470
+ const {commitment, config} =
3471
+ extractCommitmentFromConfig(commitmentOrConfig);
2672
3472
  const keys = publicKeys.map(key => key.toBase58());
2673
- const args = this._buildArgs([keys], commitment, 'base64');
3473
+ const args = this._buildArgs([keys], commitment, 'base64', config);
2674
3474
  const unsafeRes = await this._rpcRequest('getMultipleAccounts', args);
2675
3475
  const res = create(
2676
3476
  unsafeRes,
2677
3477
  jsonRpcResultAndContext(array(nullable(AccountInfoResult))),
2678
3478
  );
2679
3479
  if ('error' in res) {
2680
- throw new Error(
2681
- 'failed to get info for accounts ' + keys + ': ' + res.error.message,
3480
+ throw new SolanaJSONRPCError(
3481
+ res.error,
3482
+ `failed to get info for accounts ${keys}`,
2682
3483
  );
2683
3484
  }
2684
3485
  return res.result;
@@ -2689,11 +3490,11 @@ export class Connection {
2689
3490
  */
2690
3491
  async getMultipleAccountsInfo(
2691
3492
  publicKeys: PublicKey[],
2692
- commitment?: Commitment,
3493
+ commitmentOrConfig?: Commitment | GetMultipleAccountsConfig,
2693
3494
  ): Promise<(AccountInfo<Buffer> | null)[]> {
2694
3495
  const res = await this.getMultipleAccountsInfoAndContext(
2695
3496
  publicKeys,
2696
- commitment,
3497
+ commitmentOrConfig,
2697
3498
  );
2698
3499
  return res.value;
2699
3500
  }
@@ -2703,23 +3504,27 @@ export class Connection {
2703
3504
  */
2704
3505
  async getStakeActivation(
2705
3506
  publicKey: PublicKey,
2706
- commitment?: Commitment,
3507
+ commitmentOrConfig?: Commitment | GetStakeActivationConfig,
2707
3508
  epoch?: number,
2708
3509
  ): Promise<StakeActivationData> {
3510
+ const {commitment, config} =
3511
+ extractCommitmentFromConfig(commitmentOrConfig);
2709
3512
  const args = this._buildArgs(
2710
3513
  [publicKey.toBase58()],
2711
3514
  commitment,
2712
- undefined,
2713
- epoch !== undefined ? {epoch} : undefined,
3515
+ undefined /* encoding */,
3516
+ {
3517
+ ...config,
3518
+ epoch: epoch != null ? epoch : config?.epoch,
3519
+ },
2714
3520
  );
2715
3521
 
2716
3522
  const unsafeRes = await this._rpcRequest('getStakeActivation', args);
2717
3523
  const res = create(unsafeRes, jsonRpcResult(StakeActivationResult));
2718
3524
  if ('error' in res) {
2719
- throw new Error(
2720
- `failed to get Stake Activation ${publicKey.toBase58()}: ${
2721
- res.error.message
2722
- }`,
3525
+ throw new SolanaJSONRPCError(
3526
+ res.error,
3527
+ `failed to get Stake Activation ${publicKey.toBase58()}`,
2723
3528
  );
2724
3529
  }
2725
3530
  return res.result;
@@ -2734,40 +3539,21 @@ export class Connection {
2734
3539
  programId: PublicKey,
2735
3540
  configOrCommitment?: GetProgramAccountsConfig | Commitment,
2736
3541
  ): Promise<Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>> {
2737
- const extra: Pick<GetProgramAccountsConfig, 'dataSlice' | 'filters'> = {};
2738
-
2739
- let commitment;
2740
- let encoding;
2741
- if (configOrCommitment) {
2742
- if (typeof configOrCommitment === 'string') {
2743
- commitment = configOrCommitment;
2744
- } else {
2745
- commitment = configOrCommitment.commitment;
2746
- encoding = configOrCommitment.encoding;
2747
-
2748
- if (configOrCommitment.dataSlice) {
2749
- extra.dataSlice = configOrCommitment.dataSlice;
2750
- }
2751
- if (configOrCommitment.filters) {
2752
- extra.filters = configOrCommitment.filters;
2753
- }
2754
- }
2755
- }
2756
-
3542
+ const {commitment, config} =
3543
+ extractCommitmentFromConfig(configOrCommitment);
3544
+ const {encoding, ...configWithoutEncoding} = config || {};
2757
3545
  const args = this._buildArgs(
2758
3546
  [programId.toBase58()],
2759
3547
  commitment,
2760
3548
  encoding || 'base64',
2761
- extra,
3549
+ configWithoutEncoding,
2762
3550
  );
2763
3551
  const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
2764
3552
  const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult)));
2765
3553
  if ('error' in res) {
2766
- throw new Error(
2767
- 'failed to get accounts owned by program ' +
2768
- programId.toBase58() +
2769
- ': ' +
2770
- res.error.message,
3554
+ throw new SolanaJSONRPCError(
3555
+ res.error,
3556
+ `failed to get accounts owned by program ${programId.toBase58()}`,
2771
3557
  );
2772
3558
  }
2773
3559
  return res.result;
@@ -2787,26 +3573,13 @@ export class Connection {
2787
3573
  account: AccountInfo<Buffer | ParsedAccountData>;
2788
3574
  }>
2789
3575
  > {
2790
- const extra: Pick<GetParsedProgramAccountsConfig, 'filters'> = {};
2791
-
2792
- let commitment;
2793
- if (configOrCommitment) {
2794
- if (typeof configOrCommitment === 'string') {
2795
- commitment = configOrCommitment;
2796
- } else {
2797
- commitment = configOrCommitment.commitment;
2798
-
2799
- if (configOrCommitment.filters) {
2800
- extra.filters = configOrCommitment.filters;
2801
- }
2802
- }
2803
- }
2804
-
3576
+ const {commitment, config} =
3577
+ extractCommitmentFromConfig(configOrCommitment);
2805
3578
  const args = this._buildArgs(
2806
3579
  [programId.toBase58()],
2807
3580
  commitment,
2808
3581
  'jsonParsed',
2809
- extra,
3582
+ config,
2810
3583
  );
2811
3584
  const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
2812
3585
  const res = create(
@@ -2814,90 +3587,460 @@ export class Connection {
2814
3587
  jsonRpcResult(array(KeyedParsedAccountInfoResult)),
2815
3588
  );
2816
3589
  if ('error' in res) {
2817
- throw new Error(
2818
- 'failed to get accounts owned by program ' +
2819
- programId.toBase58() +
2820
- ': ' +
2821
- res.error.message,
3590
+ throw new SolanaJSONRPCError(
3591
+ res.error,
3592
+ `failed to get accounts owned by program ${programId.toBase58()}`,
2822
3593
  );
2823
3594
  }
2824
3595
  return res.result;
2825
3596
  }
2826
3597
 
2827
- /**
2828
- * Confirm the transaction identified by the specified signature.
2829
- */
3598
+ confirmTransaction(
3599
+ strategy:
3600
+ | BlockheightBasedTransactionConfirmationStrategy
3601
+ | DurableNonceTransactionConfirmationStrategy,
3602
+ commitment?: Commitment,
3603
+ ): Promise<RpcResponseAndContext<SignatureResult>>;
3604
+
3605
+ /** @deprecated Instead, call `confirmTransaction` using a `TransactionConfirmationConfig` */
3606
+ // eslint-disable-next-line no-dupe-class-members
3607
+ confirmTransaction(
3608
+ strategy: TransactionSignature,
3609
+ commitment?: Commitment,
3610
+ ): Promise<RpcResponseAndContext<SignatureResult>>;
3611
+
3612
+ // eslint-disable-next-line no-dupe-class-members
2830
3613
  async confirmTransaction(
2831
- signature: TransactionSignature,
3614
+ strategy:
3615
+ | BlockheightBasedTransactionConfirmationStrategy
3616
+ | DurableNonceTransactionConfirmationStrategy
3617
+ | TransactionSignature,
2832
3618
  commitment?: Commitment,
2833
3619
  ): Promise<RpcResponseAndContext<SignatureResult>> {
3620
+ let rawSignature: string;
3621
+
3622
+ if (typeof strategy == 'string') {
3623
+ rawSignature = strategy;
3624
+ } else {
3625
+ const config = strategy as
3626
+ | BlockheightBasedTransactionConfirmationStrategy
3627
+ | DurableNonceTransactionConfirmationStrategy;
3628
+ if (config.abortSignal?.aborted) {
3629
+ return Promise.reject(config.abortSignal.reason);
3630
+ }
3631
+ rawSignature = config.signature;
3632
+ }
3633
+
2834
3634
  let decodedSignature;
3635
+
2835
3636
  try {
2836
- decodedSignature = bs58.decode(signature);
3637
+ decodedSignature = bs58.decode(rawSignature);
2837
3638
  } catch (err) {
2838
- throw new Error('signature must be base58 encoded: ' + signature);
3639
+ throw new Error('signature must be base58 encoded: ' + rawSignature);
2839
3640
  }
2840
3641
 
2841
3642
  assert(decodedSignature.length === 64, 'signature has invalid length');
2842
3643
 
2843
- const start = Date.now();
2844
- const subscriptionCommitment = commitment || this.commitment;
3644
+ if (typeof strategy === 'string') {
3645
+ return await this.confirmTransactionUsingLegacyTimeoutStrategy({
3646
+ commitment: commitment || this.commitment,
3647
+ signature: rawSignature,
3648
+ });
3649
+ } else if ('lastValidBlockHeight' in strategy) {
3650
+ return await this.confirmTransactionUsingBlockHeightExceedanceStrategy({
3651
+ commitment: commitment || this.commitment,
3652
+ strategy,
3653
+ });
3654
+ } else {
3655
+ return await this.confirmTransactionUsingDurableNonceStrategy({
3656
+ commitment: commitment || this.commitment,
3657
+ strategy,
3658
+ });
3659
+ }
3660
+ }
3661
+
3662
+ private getCancellationPromise(signal?: AbortSignal): Promise<never> {
3663
+ return new Promise<never>((_, reject) => {
3664
+ if (signal == null) {
3665
+ return;
3666
+ }
3667
+ if (signal.aborted) {
3668
+ reject(signal.reason);
3669
+ } else {
3670
+ signal.addEventListener('abort', () => {
3671
+ reject(signal.reason);
3672
+ });
3673
+ }
3674
+ });
3675
+ }
2845
3676
 
2846
- let subscriptionId;
2847
- let response: RpcResponseAndContext<SignatureResult> | null = null;
2848
- const confirmPromise = new Promise((resolve, reject) => {
3677
+ private getTransactionConfirmationPromise({
3678
+ commitment,
3679
+ signature,
3680
+ }: {
3681
+ commitment?: Commitment;
3682
+ signature: string;
3683
+ }): {
3684
+ abortConfirmation(): void;
3685
+ confirmationPromise: Promise<{
3686
+ __type: TransactionStatus.PROCESSED;
3687
+ response: RpcResponseAndContext<SignatureResult>;
3688
+ }>;
3689
+ } {
3690
+ let signatureSubscriptionId: number | undefined;
3691
+ let disposeSignatureSubscriptionStateChangeObserver:
3692
+ | SubscriptionStateChangeDisposeFn
3693
+ | undefined;
3694
+ let done = false;
3695
+ const confirmationPromise = new Promise<{
3696
+ __type: TransactionStatus.PROCESSED;
3697
+ response: RpcResponseAndContext<SignatureResult>;
3698
+ }>((resolve, reject) => {
2849
3699
  try {
2850
- subscriptionId = this.onSignature(
3700
+ signatureSubscriptionId = this.onSignature(
2851
3701
  signature,
2852
3702
  (result: SignatureResult, context: Context) => {
2853
- subscriptionId = undefined;
2854
- response = {
3703
+ signatureSubscriptionId = undefined;
3704
+ const response = {
2855
3705
  context,
2856
3706
  value: result,
2857
3707
  };
2858
- resolve(null);
3708
+ resolve({__type: TransactionStatus.PROCESSED, response});
3709
+ },
3710
+ commitment,
3711
+ );
3712
+ const subscriptionSetupPromise = new Promise<void>(
3713
+ resolveSubscriptionSetup => {
3714
+ if (signatureSubscriptionId == null) {
3715
+ resolveSubscriptionSetup();
3716
+ } else {
3717
+ disposeSignatureSubscriptionStateChangeObserver =
3718
+ this._onSubscriptionStateChange(
3719
+ signatureSubscriptionId,
3720
+ nextState => {
3721
+ if (nextState === 'subscribed') {
3722
+ resolveSubscriptionSetup();
3723
+ }
3724
+ },
3725
+ );
3726
+ }
2859
3727
  },
2860
- subscriptionCommitment,
2861
3728
  );
3729
+ (async () => {
3730
+ await subscriptionSetupPromise;
3731
+ if (done) return;
3732
+ const response = await this.getSignatureStatus(signature);
3733
+ if (done) return;
3734
+ if (response == null) {
3735
+ return;
3736
+ }
3737
+ const {context, value} = response;
3738
+ if (value == null) {
3739
+ return;
3740
+ }
3741
+ if (value?.err) {
3742
+ reject(value.err);
3743
+ } else {
3744
+ switch (commitment) {
3745
+ case 'confirmed':
3746
+ case 'single':
3747
+ case 'singleGossip': {
3748
+ if (value.confirmationStatus === 'processed') {
3749
+ return;
3750
+ }
3751
+ break;
3752
+ }
3753
+ case 'finalized':
3754
+ case 'max':
3755
+ case 'root': {
3756
+ if (
3757
+ value.confirmationStatus === 'processed' ||
3758
+ value.confirmationStatus === 'confirmed'
3759
+ ) {
3760
+ return;
3761
+ }
3762
+ break;
3763
+ }
3764
+ // exhaust enums to ensure full coverage
3765
+ case 'processed':
3766
+ case 'recent':
3767
+ }
3768
+ done = true;
3769
+ resolve({
3770
+ __type: TransactionStatus.PROCESSED,
3771
+ response: {
3772
+ context,
3773
+ value,
3774
+ },
3775
+ });
3776
+ }
3777
+ })();
2862
3778
  } catch (err) {
2863
3779
  reject(err);
2864
3780
  }
2865
3781
  });
3782
+ const abortConfirmation = () => {
3783
+ if (disposeSignatureSubscriptionStateChangeObserver) {
3784
+ disposeSignatureSubscriptionStateChangeObserver();
3785
+ disposeSignatureSubscriptionStateChangeObserver = undefined;
3786
+ }
3787
+ if (signatureSubscriptionId != null) {
3788
+ this.removeSignatureListener(signatureSubscriptionId);
3789
+ signatureSubscriptionId = undefined;
3790
+ }
3791
+ };
3792
+ return {abortConfirmation, confirmationPromise};
3793
+ }
2866
3794
 
2867
- let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
2868
- switch (subscriptionCommitment) {
2869
- case 'processed':
2870
- case 'recent':
2871
- case 'single':
2872
- case 'confirmed':
2873
- case 'singleGossip': {
2874
- timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
2875
- break;
3795
+ private async confirmTransactionUsingBlockHeightExceedanceStrategy({
3796
+ commitment,
3797
+ strategy: {abortSignal, lastValidBlockHeight, signature},
3798
+ }: {
3799
+ commitment?: Commitment;
3800
+ strategy: BlockheightBasedTransactionConfirmationStrategy;
3801
+ }) {
3802
+ let done: boolean = false;
3803
+ const expiryPromise = new Promise<{
3804
+ __type: TransactionStatus.BLOCKHEIGHT_EXCEEDED;
3805
+ }>(resolve => {
3806
+ const checkBlockHeight = async () => {
3807
+ try {
3808
+ const blockHeight = await this.getBlockHeight(commitment);
3809
+ return blockHeight;
3810
+ } catch (_e) {
3811
+ return -1;
3812
+ }
3813
+ };
3814
+ (async () => {
3815
+ let currentBlockHeight = await checkBlockHeight();
3816
+ if (done) return;
3817
+ while (currentBlockHeight <= lastValidBlockHeight) {
3818
+ await sleep(1000);
3819
+ if (done) return;
3820
+ currentBlockHeight = await checkBlockHeight();
3821
+ if (done) return;
3822
+ }
3823
+ resolve({__type: TransactionStatus.BLOCKHEIGHT_EXCEEDED});
3824
+ })();
3825
+ });
3826
+ const {abortConfirmation, confirmationPromise} =
3827
+ this.getTransactionConfirmationPromise({commitment, signature});
3828
+ const cancellationPromise = this.getCancellationPromise(abortSignal);
3829
+ let result: RpcResponseAndContext<SignatureResult>;
3830
+ try {
3831
+ const outcome = await Promise.race([
3832
+ cancellationPromise,
3833
+ confirmationPromise,
3834
+ expiryPromise,
3835
+ ]);
3836
+ if (outcome.__type === TransactionStatus.PROCESSED) {
3837
+ result = outcome.response;
3838
+ } else {
3839
+ throw new TransactionExpiredBlockheightExceededError(signature);
2876
3840
  }
2877
- // exhaust enums to ensure full coverage
2878
- case 'finalized':
2879
- case 'max':
2880
- case 'root':
3841
+ } finally {
3842
+ done = true;
3843
+ abortConfirmation();
2881
3844
  }
3845
+ return result;
3846
+ }
2882
3847
 
3848
+ private async confirmTransactionUsingDurableNonceStrategy({
3849
+ commitment,
3850
+ strategy: {
3851
+ abortSignal,
3852
+ minContextSlot,
3853
+ nonceAccountPubkey,
3854
+ nonceValue,
3855
+ signature,
3856
+ },
3857
+ }: {
3858
+ commitment?: Commitment;
3859
+ strategy: DurableNonceTransactionConfirmationStrategy;
3860
+ }) {
3861
+ let done: boolean = false;
3862
+ const expiryPromise = new Promise<{
3863
+ __type: TransactionStatus.NONCE_INVALID;
3864
+ slotInWhichNonceDidAdvance: number | null;
3865
+ }>(resolve => {
3866
+ let currentNonceValue: string | undefined = nonceValue;
3867
+ let lastCheckedSlot: number | null = null;
3868
+ const getCurrentNonceValue = async () => {
3869
+ try {
3870
+ const {context, value: nonceAccount} = await this.getNonceAndContext(
3871
+ nonceAccountPubkey,
3872
+ {
3873
+ commitment,
3874
+ minContextSlot,
3875
+ },
3876
+ );
3877
+ lastCheckedSlot = context.slot;
3878
+ return nonceAccount?.nonce;
3879
+ } catch (e) {
3880
+ // If for whatever reason we can't reach/read the nonce
3881
+ // account, just keep using the last-known value.
3882
+ return currentNonceValue;
3883
+ }
3884
+ };
3885
+ (async () => {
3886
+ currentNonceValue = await getCurrentNonceValue();
3887
+ if (done) return;
3888
+ while (
3889
+ true // eslint-disable-line no-constant-condition
3890
+ ) {
3891
+ if (nonceValue !== currentNonceValue) {
3892
+ resolve({
3893
+ __type: TransactionStatus.NONCE_INVALID,
3894
+ slotInWhichNonceDidAdvance: lastCheckedSlot,
3895
+ });
3896
+ return;
3897
+ }
3898
+ await sleep(2000);
3899
+ if (done) return;
3900
+ currentNonceValue = await getCurrentNonceValue();
3901
+ if (done) return;
3902
+ }
3903
+ })();
3904
+ });
3905
+ const {abortConfirmation, confirmationPromise} =
3906
+ this.getTransactionConfirmationPromise({commitment, signature});
3907
+ const cancellationPromise = this.getCancellationPromise(abortSignal);
3908
+ let result: RpcResponseAndContext<SignatureResult>;
2883
3909
  try {
2884
- await promiseTimeout(confirmPromise, timeoutMs);
2885
- } finally {
2886
- if (subscriptionId) {
2887
- this.removeSignatureListener(subscriptionId);
3910
+ const outcome = await Promise.race([
3911
+ cancellationPromise,
3912
+ confirmationPromise,
3913
+ expiryPromise,
3914
+ ]);
3915
+ if (outcome.__type === TransactionStatus.PROCESSED) {
3916
+ result = outcome.response;
3917
+ } else {
3918
+ // Double check that the transaction is indeed unconfirmed.
3919
+ let signatureStatus:
3920
+ | RpcResponseAndContext<SignatureStatus | null>
3921
+ | null
3922
+ | undefined;
3923
+ while (
3924
+ true // eslint-disable-line no-constant-condition
3925
+ ) {
3926
+ const status = await this.getSignatureStatus(signature);
3927
+ if (status == null) {
3928
+ break;
3929
+ }
3930
+ if (
3931
+ status.context.slot <
3932
+ (outcome.slotInWhichNonceDidAdvance ?? minContextSlot)
3933
+ ) {
3934
+ await sleep(400);
3935
+ continue;
3936
+ }
3937
+ signatureStatus = status;
3938
+ break;
3939
+ }
3940
+ if (signatureStatus?.value) {
3941
+ const commitmentForStatus = commitment || 'finalized';
3942
+ const {confirmationStatus} = signatureStatus.value;
3943
+ switch (commitmentForStatus) {
3944
+ case 'processed':
3945
+ case 'recent':
3946
+ if (
3947
+ confirmationStatus !== 'processed' &&
3948
+ confirmationStatus !== 'confirmed' &&
3949
+ confirmationStatus !== 'finalized'
3950
+ ) {
3951
+ throw new TransactionExpiredNonceInvalidError(signature);
3952
+ }
3953
+ break;
3954
+ case 'confirmed':
3955
+ case 'single':
3956
+ case 'singleGossip':
3957
+ if (
3958
+ confirmationStatus !== 'confirmed' &&
3959
+ confirmationStatus !== 'finalized'
3960
+ ) {
3961
+ throw new TransactionExpiredNonceInvalidError(signature);
3962
+ }
3963
+ break;
3964
+ case 'finalized':
3965
+ case 'max':
3966
+ case 'root':
3967
+ if (confirmationStatus !== 'finalized') {
3968
+ throw new TransactionExpiredNonceInvalidError(signature);
3969
+ }
3970
+ break;
3971
+ default:
3972
+ // Exhaustive switch.
3973
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3974
+ ((_: never) => {})(commitmentForStatus);
3975
+ }
3976
+ result = {
3977
+ context: signatureStatus.context,
3978
+ value: {err: signatureStatus.value.err},
3979
+ };
3980
+ } else {
3981
+ throw new TransactionExpiredNonceInvalidError(signature);
3982
+ }
2888
3983
  }
3984
+ } finally {
3985
+ done = true;
3986
+ abortConfirmation();
2889
3987
  }
3988
+ return result;
3989
+ }
2890
3990
 
2891
- if (response === null) {
2892
- const duration = (Date.now() - start) / 1000;
2893
- throw new Error(
2894
- `Transaction was not confirmed in ${duration.toFixed(
2895
- 2,
2896
- )} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`,
3991
+ private async confirmTransactionUsingLegacyTimeoutStrategy({
3992
+ commitment,
3993
+ signature,
3994
+ }: {
3995
+ commitment?: Commitment;
3996
+ signature: string;
3997
+ }) {
3998
+ let timeoutId;
3999
+ const expiryPromise = new Promise<{
4000
+ __type: TransactionStatus.TIMED_OUT;
4001
+ timeoutMs: number;
4002
+ }>(resolve => {
4003
+ let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
4004
+ switch (commitment) {
4005
+ case 'processed':
4006
+ case 'recent':
4007
+ case 'single':
4008
+ case 'confirmed':
4009
+ case 'singleGossip': {
4010
+ timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000;
4011
+ break;
4012
+ }
4013
+ // exhaust enums to ensure full coverage
4014
+ case 'finalized':
4015
+ case 'max':
4016
+ case 'root':
4017
+ }
4018
+ timeoutId = setTimeout(
4019
+ () => resolve({__type: TransactionStatus.TIMED_OUT, timeoutMs}),
4020
+ timeoutMs,
2897
4021
  );
4022
+ });
4023
+ const {abortConfirmation, confirmationPromise} =
4024
+ this.getTransactionConfirmationPromise({
4025
+ commitment,
4026
+ signature,
4027
+ });
4028
+ let result: RpcResponseAndContext<SignatureResult>;
4029
+ try {
4030
+ const outcome = await Promise.race([confirmationPromise, expiryPromise]);
4031
+ if (outcome.__type === TransactionStatus.PROCESSED) {
4032
+ result = outcome.response;
4033
+ } else {
4034
+ throw new TransactionExpiredTimeoutError(
4035
+ signature,
4036
+ outcome.timeoutMs / 1000,
4037
+ );
4038
+ }
4039
+ } finally {
4040
+ clearTimeout(timeoutId);
4041
+ abortConfirmation();
2898
4042
  }
2899
-
2900
- return response;
4043
+ return result;
2901
4044
  }
2902
4045
 
2903
4046
  /**
@@ -2907,7 +4050,7 @@ export class Connection {
2907
4050
  const unsafeRes = await this._rpcRequest('getClusterNodes', []);
2908
4051
  const res = create(unsafeRes, jsonRpcResult(array(ContactInfoResult)));
2909
4052
  if ('error' in res) {
2910
- throw new Error('failed to get cluster nodes: ' + res.error.message);
4053
+ throw new SolanaJSONRPCError(res.error, 'failed to get cluster nodes');
2911
4054
  }
2912
4055
  return res.result;
2913
4056
  }
@@ -2920,7 +4063,7 @@ export class Connection {
2920
4063
  const unsafeRes = await this._rpcRequest('getVoteAccounts', args);
2921
4064
  const res = create(unsafeRes, GetVoteAccounts);
2922
4065
  if ('error' in res) {
2923
- throw new Error('failed to get vote accounts: ' + res.error.message);
4066
+ throw new SolanaJSONRPCError(res.error, 'failed to get vote accounts');
2924
4067
  }
2925
4068
  return res.result;
2926
4069
  }
@@ -2928,12 +4071,21 @@ export class Connection {
2928
4071
  /**
2929
4072
  * Fetch the current slot that the node is processing
2930
4073
  */
2931
- async getSlot(commitment?: Commitment): Promise<number> {
2932
- const args = this._buildArgs([], commitment);
4074
+ async getSlot(
4075
+ commitmentOrConfig?: Commitment | GetSlotConfig,
4076
+ ): Promise<number> {
4077
+ const {commitment, config} =
4078
+ extractCommitmentFromConfig(commitmentOrConfig);
4079
+ const args = this._buildArgs(
4080
+ [],
4081
+ commitment,
4082
+ undefined /* encoding */,
4083
+ config,
4084
+ );
2933
4085
  const unsafeRes = await this._rpcRequest('getSlot', args);
2934
4086
  const res = create(unsafeRes, jsonRpcResult(number()));
2935
4087
  if ('error' in res) {
2936
- throw new Error('failed to get slot: ' + res.error.message);
4088
+ throw new SolanaJSONRPCError(res.error, 'failed to get slot');
2937
4089
  }
2938
4090
  return res.result;
2939
4091
  }
@@ -2941,12 +4093,21 @@ export class Connection {
2941
4093
  /**
2942
4094
  * Fetch the current slot leader of the cluster
2943
4095
  */
2944
- async getSlotLeader(commitment?: Commitment): Promise<string> {
2945
- const args = this._buildArgs([], commitment);
4096
+ async getSlotLeader(
4097
+ commitmentOrConfig?: Commitment | GetSlotLeaderConfig,
4098
+ ): Promise<string> {
4099
+ const {commitment, config} =
4100
+ extractCommitmentFromConfig(commitmentOrConfig);
4101
+ const args = this._buildArgs(
4102
+ [],
4103
+ commitment,
4104
+ undefined /* encoding */,
4105
+ config,
4106
+ );
2946
4107
  const unsafeRes = await this._rpcRequest('getSlotLeader', args);
2947
4108
  const res = create(unsafeRes, jsonRpcResult(string()));
2948
4109
  if ('error' in res) {
2949
- throw new Error('failed to get slot leader: ' + res.error.message);
4110
+ throw new SolanaJSONRPCError(res.error, 'failed to get slot leader');
2950
4111
  }
2951
4112
  return res.result;
2952
4113
  }
@@ -2965,7 +4126,7 @@ export class Connection {
2965
4126
  const unsafeRes = await this._rpcRequest('getSlotLeaders', args);
2966
4127
  const res = create(unsafeRes, jsonRpcResult(array(PublicKeyFromString)));
2967
4128
  if ('error' in res) {
2968
- throw new Error('failed to get slot leaders: ' + res.error.message);
4129
+ throw new SolanaJSONRPCError(res.error, 'failed to get slot leaders');
2969
4130
  }
2970
4131
  return res.result;
2971
4132
  }
@@ -3000,7 +4161,7 @@ export class Connection {
3000
4161
  const unsafeRes = await this._rpcRequest('getSignatureStatuses', params);
3001
4162
  const res = create(unsafeRes, GetSignatureStatusesRpcResult);
3002
4163
  if ('error' in res) {
3003
- throw new Error('failed to get signature status: ' + res.error.message);
4164
+ throw new SolanaJSONRPCError(res.error, 'failed to get signature status');
3004
4165
  }
3005
4166
  return res.result;
3006
4167
  }
@@ -3008,12 +4169,24 @@ export class Connection {
3008
4169
  /**
3009
4170
  * Fetch the current transaction count of the cluster
3010
4171
  */
3011
- async getTransactionCount(commitment?: Commitment): Promise<number> {
3012
- const args = this._buildArgs([], commitment);
4172
+ async getTransactionCount(
4173
+ commitmentOrConfig?: Commitment | GetTransactionCountConfig,
4174
+ ): Promise<number> {
4175
+ const {commitment, config} =
4176
+ extractCommitmentFromConfig(commitmentOrConfig);
4177
+ const args = this._buildArgs(
4178
+ [],
4179
+ commitment,
4180
+ undefined /* encoding */,
4181
+ config,
4182
+ );
3013
4183
  const unsafeRes = await this._rpcRequest('getTransactionCount', args);
3014
4184
  const res = create(unsafeRes, jsonRpcResult(number()));
3015
4185
  if ('error' in res) {
3016
- throw new Error('failed to get transaction count: ' + res.error.message);
4186
+ throw new SolanaJSONRPCError(
4187
+ res.error,
4188
+ 'failed to get transaction count',
4189
+ );
3017
4190
  }
3018
4191
  return res.result;
3019
4192
  }
@@ -3041,7 +4214,7 @@ export class Connection {
3041
4214
  const unsafeRes = await this._rpcRequest('getInflationGovernor', args);
3042
4215
  const res = create(unsafeRes, GetInflationGovernorRpcResult);
3043
4216
  if ('error' in res) {
3044
- throw new Error('failed to get inflation: ' + res.error.message);
4217
+ throw new SolanaJSONRPCError(res.error, 'failed to get inflation');
3045
4218
  }
3046
4219
  return res.result;
3047
4220
  }
@@ -3052,20 +4225,23 @@ export class Connection {
3052
4225
  async getInflationReward(
3053
4226
  addresses: PublicKey[],
3054
4227
  epoch?: number,
3055
- commitment?: Commitment,
4228
+ commitmentOrConfig?: Commitment | GetInflationRewardConfig,
3056
4229
  ): Promise<(InflationReward | null)[]> {
4230
+ const {commitment, config} =
4231
+ extractCommitmentFromConfig(commitmentOrConfig);
3057
4232
  const args = this._buildArgs(
3058
4233
  [addresses.map(pubkey => pubkey.toBase58())],
3059
4234
  commitment,
3060
- undefined,
4235
+ undefined /* encoding */,
3061
4236
  {
3062
- epoch,
4237
+ ...config,
4238
+ epoch: epoch != null ? epoch : config?.epoch,
3063
4239
  },
3064
4240
  );
3065
4241
  const unsafeRes = await this._rpcRequest('getInflationReward', args);
3066
4242
  const res = create(unsafeRes, GetInflationRewardResult);
3067
4243
  if ('error' in res) {
3068
- throw new Error('failed to get inflation reward: ' + res.error.message);
4244
+ throw new SolanaJSONRPCError(res.error, 'failed to get inflation reward');
3069
4245
  }
3070
4246
  return res.result;
3071
4247
  }
@@ -3073,12 +4249,21 @@ export class Connection {
3073
4249
  /**
3074
4250
  * Fetch the Epoch Info parameters
3075
4251
  */
3076
- async getEpochInfo(commitment?: Commitment): Promise<EpochInfo> {
3077
- const args = this._buildArgs([], commitment);
4252
+ async getEpochInfo(
4253
+ commitmentOrConfig?: Commitment | GetEpochInfoConfig,
4254
+ ): Promise<EpochInfo> {
4255
+ const {commitment, config} =
4256
+ extractCommitmentFromConfig(commitmentOrConfig);
4257
+ const args = this._buildArgs(
4258
+ [],
4259
+ commitment,
4260
+ undefined /* encoding */,
4261
+ config,
4262
+ );
3078
4263
  const unsafeRes = await this._rpcRequest('getEpochInfo', args);
3079
4264
  const res = create(unsafeRes, GetEpochInfoRpcResult);
3080
4265
  if ('error' in res) {
3081
- throw new Error('failed to get epoch info: ' + res.error.message);
4266
+ throw new SolanaJSONRPCError(res.error, 'failed to get epoch info');
3082
4267
  }
3083
4268
  return res.result;
3084
4269
  }
@@ -3090,7 +4275,7 @@ export class Connection {
3090
4275
  const unsafeRes = await this._rpcRequest('getEpochSchedule', []);
3091
4276
  const res = create(unsafeRes, GetEpochScheduleRpcResult);
3092
4277
  if ('error' in res) {
3093
- throw new Error('failed to get epoch schedule: ' + res.error.message);
4278
+ throw new SolanaJSONRPCError(res.error, 'failed to get epoch schedule');
3094
4279
  }
3095
4280
  const epochSchedule = res.result;
3096
4281
  return new EpochSchedule(
@@ -3110,7 +4295,7 @@ export class Connection {
3110
4295
  const unsafeRes = await this._rpcRequest('getLeaderSchedule', []);
3111
4296
  const res = create(unsafeRes, GetLeaderScheduleRpcResult);
3112
4297
  if ('error' in res) {
3113
- throw new Error('failed to get leader schedule: ' + res.error.message);
4298
+ throw new SolanaJSONRPCError(res.error, 'failed to get leader schedule');
3114
4299
  }
3115
4300
  return res.result;
3116
4301
  }
@@ -3151,7 +4336,7 @@ export class Connection {
3151
4336
  const unsafeRes = await this._rpcRequest('getRecentBlockhash', args);
3152
4337
  const res = create(unsafeRes, GetRecentBlockhashAndContextRpcResult);
3153
4338
  if ('error' in res) {
3154
- throw new Error('failed to get recent blockhash: ' + res.error.message);
4339
+ throw new SolanaJSONRPCError(res.error, 'failed to get recent blockhash');
3155
4340
  }
3156
4341
  return res.result;
3157
4342
  }
@@ -3163,15 +4348,15 @@ export class Connection {
3163
4348
  async getRecentPerformanceSamples(
3164
4349
  limit?: number,
3165
4350
  ): Promise<Array<PerfSample>> {
3166
- const args = this._buildArgs(limit ? [limit] : []);
3167
4351
  const unsafeRes = await this._rpcRequest(
3168
4352
  'getRecentPerformanceSamples',
3169
- args,
4353
+ limit ? [limit] : [],
3170
4354
  );
3171
4355
  const res = create(unsafeRes, GetRecentPerformanceSamplesRpcResult);
3172
4356
  if ('error' in res) {
3173
- throw new Error(
3174
- 'failed to get recent performance samples: ' + res.error.message,
4357
+ throw new SolanaJSONRPCError(
4358
+ res.error,
4359
+ 'failed to get recent performance samples',
3175
4360
  );
3176
4361
  }
3177
4362
 
@@ -3195,7 +4380,7 @@ export class Connection {
3195
4380
 
3196
4381
  const res = create(unsafeRes, GetFeeCalculatorRpcResult);
3197
4382
  if ('error' in res) {
3198
- throw new Error('failed to get fee calculator: ' + res.error.message);
4383
+ throw new SolanaJSONRPCError(res.error, 'failed to get fee calculator');
3199
4384
  }
3200
4385
  const {context, value} = res.result;
3201
4386
  return {
@@ -3208,16 +4393,16 @@ export class Connection {
3208
4393
  * Fetch the fee for a message from the cluster, return with context
3209
4394
  */
3210
4395
  async getFeeForMessage(
3211
- message: Message,
4396
+ message: VersionedMessage,
3212
4397
  commitment?: Commitment,
3213
4398
  ): Promise<RpcResponseAndContext<number>> {
3214
- const wireMessage = message.serialize().toString('base64');
4399
+ const wireMessage = toBuffer(message.serialize()).toString('base64');
3215
4400
  const args = this._buildArgs([wireMessage], commitment);
3216
4401
  const unsafeRes = await this._rpcRequest('getFeeForMessage', args);
3217
4402
 
3218
4403
  const res = create(unsafeRes, jsonRpcResultAndContext(nullable(number())));
3219
4404
  if ('error' in res) {
3220
- throw new Error('failed to get slot: ' + res.error.message);
4405
+ throw new SolanaJSONRPCError(res.error, 'failed to get fee for message');
3221
4406
  }
3222
4407
  if (res.result === null) {
3223
4408
  throw new Error('invalid blockhash');
@@ -3244,13 +4429,13 @@ export class Connection {
3244
4429
 
3245
4430
  /**
3246
4431
  * Fetch the latest blockhash from the cluster
3247
- * @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
4432
+ * @return {Promise<BlockhashWithExpiryBlockHeight>}
3248
4433
  */
3249
4434
  async getLatestBlockhash(
3250
- commitment?: Commitment,
3251
- ): Promise<{blockhash: Blockhash; lastValidBlockHeight: number}> {
4435
+ commitmentOrConfig?: Commitment | GetLatestBlockhashConfig,
4436
+ ): Promise<BlockhashWithExpiryBlockHeight> {
3252
4437
  try {
3253
- const res = await this.getLatestBlockhashAndContext(commitment);
4438
+ const res = await this.getLatestBlockhashAndContext(commitmentOrConfig);
3254
4439
  return res.value;
3255
4440
  } catch (e) {
3256
4441
  throw new Error('failed to get recent blockhash: ' + e);
@@ -3259,18 +4444,23 @@ export class Connection {
3259
4444
 
3260
4445
  /**
3261
4446
  * Fetch the latest blockhash from the cluster
3262
- * @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
4447
+ * @return {Promise<BlockhashWithExpiryBlockHeight>}
3263
4448
  */
3264
4449
  async getLatestBlockhashAndContext(
3265
- commitment?: Commitment,
3266
- ): Promise<
3267
- RpcResponseAndContext<{blockhash: Blockhash; lastValidBlockHeight: number}>
3268
- > {
3269
- const args = this._buildArgs([], commitment);
4450
+ commitmentOrConfig?: Commitment | GetLatestBlockhashConfig,
4451
+ ): Promise<RpcResponseAndContext<BlockhashWithExpiryBlockHeight>> {
4452
+ const {commitment, config} =
4453
+ extractCommitmentFromConfig(commitmentOrConfig);
4454
+ const args = this._buildArgs(
4455
+ [],
4456
+ commitment,
4457
+ undefined /* encoding */,
4458
+ config,
4459
+ );
3270
4460
  const unsafeRes = await this._rpcRequest('getLatestBlockhash', args);
3271
4461
  const res = create(unsafeRes, GetLatestBlockhashRpcResult);
3272
4462
  if ('error' in res) {
3273
- throw new Error('failed to get latest blockhash: ' + res.error.message);
4463
+ throw new SolanaJSONRPCError(res.error, 'failed to get latest blockhash');
3274
4464
  }
3275
4465
  return res.result;
3276
4466
  }
@@ -3282,7 +4472,7 @@ export class Connection {
3282
4472
  const unsafeRes = await this._rpcRequest('getVersion', []);
3283
4473
  const res = create(unsafeRes, jsonRpcResult(VersionResult));
3284
4474
  if ('error' in res) {
3285
- throw new Error('failed to get version: ' + res.error.message);
4475
+ throw new SolanaJSONRPCError(res.error, 'failed to get version');
3286
4476
  }
3287
4477
  return res.result;
3288
4478
  }
@@ -3294,57 +4484,220 @@ export class Connection {
3294
4484
  const unsafeRes = await this._rpcRequest('getGenesisHash', []);
3295
4485
  const res = create(unsafeRes, jsonRpcResult(string()));
3296
4486
  if ('error' in res) {
3297
- throw new Error('failed to get genesis hash: ' + res.error.message);
4487
+ throw new SolanaJSONRPCError(res.error, 'failed to get genesis hash');
3298
4488
  }
3299
4489
  return res.result;
3300
4490
  }
3301
4491
 
4492
+ /**
4493
+ * Fetch a processed block from the cluster.
4494
+ *
4495
+ * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by
4496
+ * setting the `maxSupportedTransactionVersion` property.
4497
+ */
4498
+ async getBlock(
4499
+ slot: number,
4500
+ rawConfig?: GetBlockConfig,
4501
+ ): Promise<BlockResponse | null>;
4502
+
4503
+ /**
4504
+ * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by
4505
+ * setting the `maxSupportedTransactionVersion` property.
4506
+ */
4507
+ // eslint-disable-next-line no-dupe-class-members
4508
+ async getBlock(
4509
+ slot: number,
4510
+ rawConfig: GetBlockConfig & {transactionDetails: 'accounts'},
4511
+ ): Promise<AccountsModeBlockResponse | null>;
4512
+
4513
+ /**
4514
+ * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by
4515
+ * setting the `maxSupportedTransactionVersion` property.
4516
+ */
4517
+ // eslint-disable-next-line no-dupe-class-members
4518
+ async getBlock(
4519
+ slot: number,
4520
+ rawConfig: GetBlockConfig & {transactionDetails: 'none'},
4521
+ ): Promise<NoneModeBlockResponse | null>;
4522
+
3302
4523
  /**
3303
4524
  * Fetch a processed block from the cluster.
3304
4525
  */
4526
+ // eslint-disable-next-line no-dupe-class-members
4527
+ async getBlock(
4528
+ slot: number,
4529
+ rawConfig?: GetVersionedBlockConfig,
4530
+ ): Promise<VersionedBlockResponse | null>;
4531
+
4532
+ // eslint-disable-next-line no-dupe-class-members
4533
+ async getBlock(
4534
+ slot: number,
4535
+ rawConfig: GetVersionedBlockConfig & {transactionDetails: 'accounts'},
4536
+ ): Promise<VersionedAccountsModeBlockResponse | null>;
4537
+
4538
+ // eslint-disable-next-line no-dupe-class-members
3305
4539
  async getBlock(
3306
4540
  slot: number,
3307
- opts?: {commitment?: Finality},
3308
- ): Promise<BlockResponse | null> {
4541
+ rawConfig: GetVersionedBlockConfig & {transactionDetails: 'none'},
4542
+ ): Promise<VersionedNoneModeBlockResponse | null>;
4543
+
4544
+ /**
4545
+ * Fetch a processed block from the cluster.
4546
+ */
4547
+ // eslint-disable-next-line no-dupe-class-members
4548
+ async getBlock(
4549
+ slot: number,
4550
+ rawConfig?: GetVersionedBlockConfig,
4551
+ ): Promise<
4552
+ | VersionedBlockResponse
4553
+ | VersionedAccountsModeBlockResponse
4554
+ | VersionedNoneModeBlockResponse
4555
+ | null
4556
+ > {
4557
+ const {commitment, config} = extractCommitmentFromConfig(rawConfig);
3309
4558
  const args = this._buildArgsAtLeastConfirmed(
3310
4559
  [slot],
3311
- opts && opts.commitment,
4560
+ commitment as Finality,
4561
+ undefined /* encoding */,
4562
+ config,
3312
4563
  );
3313
4564
  const unsafeRes = await this._rpcRequest('getBlock', args);
3314
- const res = create(unsafeRes, GetBlockRpcResult);
3315
-
3316
- if ('error' in res) {
3317
- throw new Error('failed to get confirmed block: ' + res.error.message);
4565
+ try {
4566
+ switch (config?.transactionDetails) {
4567
+ case 'accounts': {
4568
+ const res = create(unsafeRes, GetAccountsModeBlockRpcResult);
4569
+ if ('error' in res) {
4570
+ throw res.error;
4571
+ }
4572
+ return res.result;
4573
+ }
4574
+ case 'none': {
4575
+ const res = create(unsafeRes, GetNoneModeBlockRpcResult);
4576
+ if ('error' in res) {
4577
+ throw res.error;
4578
+ }
4579
+ return res.result;
4580
+ }
4581
+ default: {
4582
+ const res = create(unsafeRes, GetBlockRpcResult);
4583
+ if ('error' in res) {
4584
+ throw res.error;
4585
+ }
4586
+ const {result} = res;
4587
+ return result
4588
+ ? {
4589
+ ...result,
4590
+ transactions: result.transactions.map(
4591
+ ({transaction, meta, version}) => ({
4592
+ meta,
4593
+ transaction: {
4594
+ ...transaction,
4595
+ message: versionedMessageFromResponse(
4596
+ version,
4597
+ transaction.message,
4598
+ ),
4599
+ },
4600
+ version,
4601
+ }),
4602
+ ),
4603
+ }
4604
+ : null;
4605
+ }
4606
+ }
4607
+ } catch (e) {
4608
+ throw new SolanaJSONRPCError(
4609
+ e as JSONRPCError,
4610
+ 'failed to get confirmed block',
4611
+ );
3318
4612
  }
4613
+ }
3319
4614
 
3320
- const result = res.result;
3321
- if (!result) return result;
4615
+ /**
4616
+ * Fetch parsed transaction details for a confirmed or finalized block
4617
+ */
4618
+ async getParsedBlock(
4619
+ slot: number,
4620
+ rawConfig?: GetVersionedBlockConfig,
4621
+ ): Promise<ParsedAccountsModeBlockResponse>;
3322
4622
 
3323
- return {
3324
- ...result,
3325
- transactions: result.transactions.map(({transaction, meta}) => {
3326
- const message = new Message(transaction.message);
3327
- return {
3328
- meta,
3329
- transaction: {
3330
- ...transaction,
3331
- message,
3332
- },
3333
- };
3334
- }),
3335
- };
4623
+ // eslint-disable-next-line no-dupe-class-members
4624
+ async getParsedBlock(
4625
+ slot: number,
4626
+ rawConfig: GetVersionedBlockConfig & {transactionDetails: 'accounts'},
4627
+ ): Promise<ParsedAccountsModeBlockResponse>;
4628
+
4629
+ // eslint-disable-next-line no-dupe-class-members
4630
+ async getParsedBlock(
4631
+ slot: number,
4632
+ rawConfig: GetVersionedBlockConfig & {transactionDetails: 'none'},
4633
+ ): Promise<ParsedNoneModeBlockResponse>;
4634
+ // eslint-disable-next-line no-dupe-class-members
4635
+ async getParsedBlock(
4636
+ slot: number,
4637
+ rawConfig?: GetVersionedBlockConfig,
4638
+ ): Promise<
4639
+ | ParsedBlockResponse
4640
+ | ParsedAccountsModeBlockResponse
4641
+ | ParsedNoneModeBlockResponse
4642
+ | null
4643
+ > {
4644
+ const {commitment, config} = extractCommitmentFromConfig(rawConfig);
4645
+ const args = this._buildArgsAtLeastConfirmed(
4646
+ [slot],
4647
+ commitment as Finality,
4648
+ 'jsonParsed',
4649
+ config,
4650
+ );
4651
+ const unsafeRes = await this._rpcRequest('getBlock', args);
4652
+ try {
4653
+ switch (config?.transactionDetails) {
4654
+ case 'accounts': {
4655
+ const res = create(unsafeRes, GetParsedAccountsModeBlockRpcResult);
4656
+ if ('error' in res) {
4657
+ throw res.error;
4658
+ }
4659
+ return res.result;
4660
+ }
4661
+ case 'none': {
4662
+ const res = create(unsafeRes, GetParsedNoneModeBlockRpcResult);
4663
+ if ('error' in res) {
4664
+ throw res.error;
4665
+ }
4666
+ return res.result;
4667
+ }
4668
+ default: {
4669
+ const res = create(unsafeRes, GetParsedBlockRpcResult);
4670
+ if ('error' in res) {
4671
+ throw res.error;
4672
+ }
4673
+ return res.result;
4674
+ }
4675
+ }
4676
+ } catch (e) {
4677
+ throw new SolanaJSONRPCError(e as JSONRPCError, 'failed to get block');
4678
+ }
3336
4679
  }
3337
4680
 
3338
4681
  /*
3339
4682
  * Returns the current block height of the node
3340
4683
  */
3341
- async getBlockHeight(commitment?: Commitment): Promise<number> {
3342
- const args = this._buildArgs([], commitment);
4684
+ async getBlockHeight(
4685
+ commitmentOrConfig?: Commitment | GetBlockHeightConfig,
4686
+ ): Promise<number> {
4687
+ const {commitment, config} =
4688
+ extractCommitmentFromConfig(commitmentOrConfig);
4689
+ const args = this._buildArgs(
4690
+ [],
4691
+ commitment,
4692
+ undefined /* encoding */,
4693
+ config,
4694
+ );
3343
4695
  const unsafeRes = await this._rpcRequest('getBlockHeight', args);
3344
4696
  const res = create(unsafeRes, jsonRpcResult(number()));
3345
4697
  if ('error' in res) {
3346
- throw new Error(
3347
- 'failed to get block height information: ' + res.error.message,
4698
+ throw new SolanaJSONRPCError(
4699
+ res.error,
4700
+ 'failed to get block height information',
3348
4701
  );
3349
4702
  }
3350
4703
 
@@ -3372,8 +4725,9 @@ export class Connection {
3372
4725
  const unsafeRes = await this._rpcRequest('getBlockProduction', args);
3373
4726
  const res = create(unsafeRes, BlockProductionResponseStruct);
3374
4727
  if ('error' in res) {
3375
- throw new Error(
3376
- 'failed to get block production information: ' + res.error.message,
4728
+ throw new SolanaJSONRPCError(
4729
+ res.error,
4730
+ 'failed to get block production information',
3377
4731
  );
3378
4732
  }
3379
4733
 
@@ -3382,19 +4736,44 @@ export class Connection {
3382
4736
 
3383
4737
  /**
3384
4738
  * Fetch a confirmed or finalized transaction from the cluster.
4739
+ *
4740
+ * @deprecated Instead, call `getTransaction` using a
4741
+ * `GetVersionedTransactionConfig` by setting the
4742
+ * `maxSupportedTransactionVersion` property.
3385
4743
  */
3386
4744
  async getTransaction(
3387
4745
  signature: string,
3388
- opts?: {commitment?: Finality},
3389
- ): Promise<TransactionResponse | null> {
4746
+ rawConfig?: GetTransactionConfig,
4747
+ ): Promise<TransactionResponse | null>;
4748
+
4749
+ /**
4750
+ * Fetch a confirmed or finalized transaction from the cluster.
4751
+ */
4752
+ // eslint-disable-next-line no-dupe-class-members
4753
+ async getTransaction(
4754
+ signature: string,
4755
+ rawConfig: GetVersionedTransactionConfig,
4756
+ ): Promise<VersionedTransactionResponse | null>;
4757
+
4758
+ /**
4759
+ * Fetch a confirmed or finalized transaction from the cluster.
4760
+ */
4761
+ // eslint-disable-next-line no-dupe-class-members
4762
+ async getTransaction(
4763
+ signature: string,
4764
+ rawConfig?: GetVersionedTransactionConfig,
4765
+ ): Promise<VersionedTransactionResponse | null> {
4766
+ const {commitment, config} = extractCommitmentFromConfig(rawConfig);
3390
4767
  const args = this._buildArgsAtLeastConfirmed(
3391
4768
  [signature],
3392
- opts && opts.commitment,
4769
+ commitment as Finality,
4770
+ undefined /* encoding */,
4771
+ config,
3393
4772
  );
3394
4773
  const unsafeRes = await this._rpcRequest('getTransaction', args);
3395
4774
  const res = create(unsafeRes, GetTransactionRpcResult);
3396
4775
  if ('error' in res) {
3397
- throw new Error('failed to get transaction: ' + res.error.message);
4776
+ throw new SolanaJSONRPCError(res.error, 'failed to get transaction');
3398
4777
  }
3399
4778
 
3400
4779
  const result = res.result;
@@ -3404,7 +4783,10 @@ export class Connection {
3404
4783
  ...result,
3405
4784
  transaction: {
3406
4785
  ...result.transaction,
3407
- message: new Message(result.transaction.message),
4786
+ message: versionedMessageFromResponse(
4787
+ result.version,
4788
+ result.transaction.message,
4789
+ ),
3408
4790
  },
3409
4791
  };
3410
4792
  }
@@ -3414,17 +4796,20 @@ export class Connection {
3414
4796
  */
3415
4797
  async getParsedTransaction(
3416
4798
  signature: TransactionSignature,
3417
- commitment?: Finality,
3418
- ): Promise<ParsedConfirmedTransaction | null> {
4799
+ commitmentOrConfig?: GetVersionedTransactionConfig | Finality,
4800
+ ): Promise<ParsedTransactionWithMeta | null> {
4801
+ const {commitment, config} =
4802
+ extractCommitmentFromConfig(commitmentOrConfig);
3419
4803
  const args = this._buildArgsAtLeastConfirmed(
3420
4804
  [signature],
3421
- commitment,
4805
+ commitment as Finality,
3422
4806
  'jsonParsed',
4807
+ config,
3423
4808
  );
3424
4809
  const unsafeRes = await this._rpcRequest('getTransaction', args);
3425
4810
  const res = create(unsafeRes, GetParsedTransactionRpcResult);
3426
4811
  if ('error' in res) {
3427
- throw new Error('failed to get transaction: ' + res.error.message);
4812
+ throw new SolanaJSONRPCError(res.error, 'failed to get transaction');
3428
4813
  }
3429
4814
  return res.result;
3430
4815
  }
@@ -3434,13 +4819,16 @@ export class Connection {
3434
4819
  */
3435
4820
  async getParsedTransactions(
3436
4821
  signatures: TransactionSignature[],
3437
- commitment?: Finality,
3438
- ): Promise<(ParsedConfirmedTransaction | null)[]> {
4822
+ commitmentOrConfig?: GetVersionedTransactionConfig | Finality,
4823
+ ): Promise<(ParsedTransactionWithMeta | null)[]> {
4824
+ const {commitment, config} =
4825
+ extractCommitmentFromConfig(commitmentOrConfig);
3439
4826
  const batch = signatures.map(signature => {
3440
4827
  const args = this._buildArgsAtLeastConfirmed(
3441
4828
  [signature],
3442
- commitment,
4829
+ commitment as Finality,
3443
4830
  'jsonParsed',
4831
+ config,
3444
4832
  );
3445
4833
  return {
3446
4834
  methodName: 'getTransaction',
@@ -3452,7 +4840,7 @@ export class Connection {
3452
4840
  const res = unsafeRes.map((unsafeRes: any) => {
3453
4841
  const res = create(unsafeRes, GetParsedTransactionRpcResult);
3454
4842
  if ('error' in res) {
3455
- throw new Error('failed to get transactions: ' + res.error.message);
4843
+ throw new SolanaJSONRPCError(res.error, 'failed to get transactions');
3456
4844
  }
3457
4845
  return res.result;
3458
4846
  });
@@ -3460,6 +4848,79 @@ export class Connection {
3460
4848
  return res;
3461
4849
  }
3462
4850
 
4851
+ /**
4852
+ * Fetch transaction details for a batch of confirmed transactions.
4853
+ * Similar to {@link getParsedTransactions} but returns a {@link TransactionResponse}.
4854
+ *
4855
+ * @deprecated Instead, call `getTransactions` using a
4856
+ * `GetVersionedTransactionConfig` by setting the
4857
+ * `maxSupportedTransactionVersion` property.
4858
+ */
4859
+ async getTransactions(
4860
+ signatures: TransactionSignature[],
4861
+ commitmentOrConfig?: GetTransactionConfig | Finality,
4862
+ ): Promise<(TransactionResponse | null)[]>;
4863
+
4864
+ /**
4865
+ * Fetch transaction details for a batch of confirmed transactions.
4866
+ * Similar to {@link getParsedTransactions} but returns a {@link
4867
+ * VersionedTransactionResponse}.
4868
+ */
4869
+ // eslint-disable-next-line no-dupe-class-members
4870
+ async getTransactions(
4871
+ signatures: TransactionSignature[],
4872
+ commitmentOrConfig: GetVersionedTransactionConfig | Finality,
4873
+ ): Promise<(VersionedTransactionResponse | null)[]>;
4874
+
4875
+ /**
4876
+ * Fetch transaction details for a batch of confirmed transactions.
4877
+ * Similar to {@link getParsedTransactions} but returns a {@link
4878
+ * VersionedTransactionResponse}.
4879
+ */
4880
+ // eslint-disable-next-line no-dupe-class-members
4881
+ async getTransactions(
4882
+ signatures: TransactionSignature[],
4883
+ commitmentOrConfig: GetVersionedTransactionConfig | Finality,
4884
+ ): Promise<(VersionedTransactionResponse | null)[]> {
4885
+ const {commitment, config} =
4886
+ extractCommitmentFromConfig(commitmentOrConfig);
4887
+ const batch = signatures.map(signature => {
4888
+ const args = this._buildArgsAtLeastConfirmed(
4889
+ [signature],
4890
+ commitment as Finality,
4891
+ undefined /* encoding */,
4892
+ config,
4893
+ );
4894
+ return {
4895
+ methodName: 'getTransaction',
4896
+ args,
4897
+ };
4898
+ });
4899
+
4900
+ const unsafeRes = await this._rpcBatchRequest(batch);
4901
+ const res = unsafeRes.map((unsafeRes: any) => {
4902
+ const res = create(unsafeRes, GetTransactionRpcResult);
4903
+ if ('error' in res) {
4904
+ throw new SolanaJSONRPCError(res.error, 'failed to get transactions');
4905
+ }
4906
+ const result = res.result;
4907
+ if (!result) return result;
4908
+
4909
+ return {
4910
+ ...result,
4911
+ transaction: {
4912
+ ...result.transaction,
4913
+ message: versionedMessageFromResponse(
4914
+ result.version,
4915
+ result.transaction.message,
4916
+ ),
4917
+ },
4918
+ };
4919
+ });
4920
+
4921
+ return res;
4922
+ }
4923
+
3463
4924
  /**
3464
4925
  * Fetch a list of Transactions and transaction statuses from the cluster
3465
4926
  * for a confirmed block.
@@ -3475,7 +4936,7 @@ export class Connection {
3475
4936
  const res = create(unsafeRes, GetConfirmedBlockRpcResult);
3476
4937
 
3477
4938
  if ('error' in res) {
3478
- throw new Error('failed to get confirmed block: ' + res.error.message);
4939
+ throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block');
3479
4940
  }
3480
4941
 
3481
4942
  const result = res.result;
@@ -3526,7 +4987,7 @@ export class Connection {
3526
4987
  const unsafeRes = await this._rpcRequest('getBlocks', args);
3527
4988
  const res = create(unsafeRes, jsonRpcResult(array(number())));
3528
4989
  if ('error' in res) {
3529
- throw new Error('failed to get blocks: ' + res.error.message);
4990
+ throw new SolanaJSONRPCError(res.error, 'failed to get blocks');
3530
4991
  }
3531
4992
  return res.result;
3532
4993
  }
@@ -3550,7 +5011,7 @@ export class Connection {
3550
5011
  const unsafeRes = await this._rpcRequest('getBlock', args);
3551
5012
  const res = create(unsafeRes, GetBlockSignaturesRpcResult);
3552
5013
  if ('error' in res) {
3553
- throw new Error('failed to get block: ' + res.error.message);
5014
+ throw new SolanaJSONRPCError(res.error, 'failed to get block');
3554
5015
  }
3555
5016
  const result = res.result;
3556
5017
  if (!result) {
@@ -3580,7 +5041,7 @@ export class Connection {
3580
5041
  const unsafeRes = await this._rpcRequest('getConfirmedBlock', args);
3581
5042
  const res = create(unsafeRes, GetBlockSignaturesRpcResult);
3582
5043
  if ('error' in res) {
3583
- throw new Error('failed to get confirmed block: ' + res.error.message);
5044
+ throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block');
3584
5045
  }
3585
5046
  const result = res.result;
3586
5047
  if (!result) {
@@ -3602,7 +5063,7 @@ export class Connection {
3602
5063
  const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args);
3603
5064
  const res = create(unsafeRes, GetTransactionRpcResult);
3604
5065
  if ('error' in res) {
3605
- throw new Error('failed to get transaction: ' + res.error.message);
5066
+ throw new SolanaJSONRPCError(res.error, 'failed to get transaction');
3606
5067
  }
3607
5068
 
3608
5069
  const result = res.result;
@@ -3633,8 +5094,9 @@ export class Connection {
3633
5094
  const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args);
3634
5095
  const res = create(unsafeRes, GetParsedTransactionRpcResult);
3635
5096
  if ('error' in res) {
3636
- throw new Error(
3637
- 'failed to get confirmed transaction: ' + res.error.message,
5097
+ throw new SolanaJSONRPCError(
5098
+ res.error,
5099
+ 'failed to get confirmed transaction',
3638
5100
  );
3639
5101
  }
3640
5102
  return res.result;
@@ -3665,8 +5127,9 @@ export class Connection {
3665
5127
  const res = unsafeRes.map((unsafeRes: any) => {
3666
5128
  const res = create(unsafeRes, GetParsedTransactionRpcResult);
3667
5129
  if ('error' in res) {
3668
- throw new Error(
3669
- 'failed to get confirmed transactions: ' + res.error.message,
5130
+ throw new SolanaJSONRPCError(
5131
+ res.error,
5132
+ 'failed to get confirmed transactions',
3670
5133
  );
3671
5134
  }
3672
5135
  return res.result;
@@ -3771,8 +5234,9 @@ export class Connection {
3771
5234
  );
3772
5235
  const res = create(unsafeRes, GetConfirmedSignaturesForAddress2RpcResult);
3773
5236
  if ('error' in res) {
3774
- throw new Error(
3775
- 'failed to get confirmed signatures for address: ' + res.error.message,
5237
+ throw new SolanaJSONRPCError(
5238
+ res.error,
5239
+ 'failed to get confirmed signatures for address',
3776
5240
  );
3777
5241
  }
3778
5242
  return res.result;
@@ -3800,23 +5264,47 @@ export class Connection {
3800
5264
  const unsafeRes = await this._rpcRequest('getSignaturesForAddress', args);
3801
5265
  const res = create(unsafeRes, GetSignaturesForAddressRpcResult);
3802
5266
  if ('error' in res) {
3803
- throw new Error(
3804
- 'failed to get signatures for address: ' + res.error.message,
5267
+ throw new SolanaJSONRPCError(
5268
+ res.error,
5269
+ 'failed to get signatures for address',
3805
5270
  );
3806
5271
  }
3807
5272
  return res.result;
3808
5273
  }
3809
5274
 
5275
+ async getAddressLookupTable(
5276
+ accountKey: PublicKey,
5277
+ config?: GetAccountInfoConfig,
5278
+ ): Promise<RpcResponseAndContext<AddressLookupTableAccount | null>> {
5279
+ const {context, value: accountInfo} = await this.getAccountInfoAndContext(
5280
+ accountKey,
5281
+ config,
5282
+ );
5283
+
5284
+ let value = null;
5285
+ if (accountInfo !== null) {
5286
+ value = new AddressLookupTableAccount({
5287
+ key: accountKey,
5288
+ state: AddressLookupTableAccount.deserialize(accountInfo.data),
5289
+ });
5290
+ }
5291
+
5292
+ return {
5293
+ context,
5294
+ value,
5295
+ };
5296
+ }
5297
+
3810
5298
  /**
3811
5299
  * Fetch the contents of a Nonce account from the cluster, return with context
3812
5300
  */
3813
5301
  async getNonceAndContext(
3814
5302
  nonceAccount: PublicKey,
3815
- commitment?: Commitment,
5303
+ commitmentOrConfig?: Commitment | GetNonceAndContextConfig,
3816
5304
  ): Promise<RpcResponseAndContext<NonceAccount | null>> {
3817
5305
  const {context, value: accountInfo} = await this.getAccountInfoAndContext(
3818
5306
  nonceAccount,
3819
- commitment,
5307
+ commitmentOrConfig,
3820
5308
  );
3821
5309
 
3822
5310
  let value = null;
@@ -3835,9 +5323,9 @@ export class Connection {
3835
5323
  */
3836
5324
  async getNonce(
3837
5325
  nonceAccount: PublicKey,
3838
- commitment?: Commitment,
5326
+ commitmentOrConfig?: Commitment | GetNonceConfig,
3839
5327
  ): Promise<NonceAccount | null> {
3840
- return await this.getNonceAndContext(nonceAccount, commitment)
5328
+ return await this.getNonceAndContext(nonceAccount, commitmentOrConfig)
3841
5329
  .then(x => x.value)
3842
5330
  .catch(e => {
3843
5331
  throw new Error(
@@ -3873,8 +5361,9 @@ export class Connection {
3873
5361
  ]);
3874
5362
  const res = create(unsafeRes, RequestAirdropRpcResult);
3875
5363
  if ('error' in res) {
3876
- throw new Error(
3877
- 'airdrop to ' + to.toBase58() + ' failed: ' + res.error.message,
5364
+ throw new SolanaJSONRPCError(
5365
+ res.error,
5366
+ `airdrop to ${to.toBase58()} failed`,
3878
5367
  );
3879
5368
  }
3880
5369
  return res.result;
@@ -3883,7 +5372,9 @@ export class Connection {
3883
5372
  /**
3884
5373
  * @internal
3885
5374
  */
3886
- async _recentBlockhash(disableCache: boolean): Promise<Blockhash> {
5375
+ async _blockhashWithExpiryBlockHeight(
5376
+ disableCache: boolean,
5377
+ ): Promise<BlockhashWithExpiryBlockHeight> {
3887
5378
  if (!disableCache) {
3888
5379
  // Wait for polling to finish
3889
5380
  while (this._pollingBlockhash) {
@@ -3891,8 +5382,8 @@ export class Connection {
3891
5382
  }
3892
5383
  const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch;
3893
5384
  const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS;
3894
- if (this._blockhashInfo.recentBlockhash !== null && !expired) {
3895
- return this._blockhashInfo.recentBlockhash;
5385
+ if (this._blockhashInfo.latestBlockhash !== null && !expired) {
5386
+ return this._blockhashInfo.latestBlockhash;
3896
5387
  }
3897
5388
  }
3898
5389
 
@@ -3902,21 +5393,25 @@ export class Connection {
3902
5393
  /**
3903
5394
  * @internal
3904
5395
  */
3905
- async _pollNewBlockhash(): Promise<Blockhash> {
5396
+ async _pollNewBlockhash(): Promise<BlockhashWithExpiryBlockHeight> {
3906
5397
  this._pollingBlockhash = true;
3907
5398
  try {
3908
5399
  const startTime = Date.now();
5400
+ const cachedLatestBlockhash = this._blockhashInfo.latestBlockhash;
5401
+ const cachedBlockhash = cachedLatestBlockhash
5402
+ ? cachedLatestBlockhash.blockhash
5403
+ : null;
3909
5404
  for (let i = 0; i < 50; i++) {
3910
- const {blockhash} = await this.getRecentBlockhash('finalized');
5405
+ const latestBlockhash = await this.getLatestBlockhash('finalized');
3911
5406
 
3912
- if (this._blockhashInfo.recentBlockhash != blockhash) {
5407
+ if (cachedBlockhash !== latestBlockhash.blockhash) {
3913
5408
  this._blockhashInfo = {
3914
- recentBlockhash: blockhash,
5409
+ latestBlockhash,
3915
5410
  lastFetch: Date.now(),
3916
5411
  transactionSignatures: [],
3917
5412
  simulatedSignatures: [],
3918
5413
  };
3919
- return blockhash;
5414
+ return latestBlockhash;
3920
5415
  }
3921
5416
 
3922
5417
  // Sleep for approximately half a slot
@@ -3931,36 +5426,108 @@ export class Connection {
3931
5426
  }
3932
5427
  }
3933
5428
 
5429
+ /**
5430
+ * get the stake minimum delegation
5431
+ */
5432
+ async getStakeMinimumDelegation(
5433
+ config?: GetStakeMinimumDelegationConfig,
5434
+ ): Promise<RpcResponseAndContext<number>> {
5435
+ const {commitment, config: configArg} = extractCommitmentFromConfig(config);
5436
+ const args = this._buildArgs([], commitment, 'base64', configArg);
5437
+ const unsafeRes = await this._rpcRequest('getStakeMinimumDelegation', args);
5438
+ const res = create(unsafeRes, jsonRpcResultAndContext(number()));
5439
+ if ('error' in res) {
5440
+ throw new SolanaJSONRPCError(
5441
+ res.error,
5442
+ `failed to get stake minimum delegation`,
5443
+ );
5444
+ }
5445
+ return res.result;
5446
+ }
5447
+
3934
5448
  /**
3935
5449
  * Simulate a transaction
5450
+ *
5451
+ * @deprecated Instead, call {@link simulateTransaction} with {@link
5452
+ * VersionedTransaction} and {@link SimulateTransactionConfig} parameters
3936
5453
  */
3937
- async simulateTransaction(
5454
+ simulateTransaction(
3938
5455
  transactionOrMessage: Transaction | Message,
3939
5456
  signers?: Array<Signer>,
3940
5457
  includeAccounts?: boolean | Array<PublicKey>,
5458
+ ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>>;
5459
+
5460
+ /**
5461
+ * Simulate a transaction
5462
+ */
5463
+ // eslint-disable-next-line no-dupe-class-members
5464
+ simulateTransaction(
5465
+ transaction: VersionedTransaction,
5466
+ config?: SimulateTransactionConfig,
5467
+ ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>>;
5468
+
5469
+ /**
5470
+ * Simulate a transaction
5471
+ */
5472
+ // eslint-disable-next-line no-dupe-class-members
5473
+ async simulateTransaction(
5474
+ transactionOrMessage: VersionedTransaction | Transaction | Message,
5475
+ configOrSigners?: SimulateTransactionConfig | Array<Signer>,
5476
+ includeAccounts?: boolean | Array<PublicKey>,
3941
5477
  ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
5478
+ if ('message' in transactionOrMessage) {
5479
+ const versionedTx = transactionOrMessage;
5480
+ const wireTransaction = versionedTx.serialize();
5481
+ const encodedTransaction =
5482
+ Buffer.from(wireTransaction).toString('base64');
5483
+ if (Array.isArray(configOrSigners) || includeAccounts !== undefined) {
5484
+ throw new Error('Invalid arguments');
5485
+ }
5486
+
5487
+ const config: any = configOrSigners || {};
5488
+ config.encoding = 'base64';
5489
+ if (!('commitment' in config)) {
5490
+ config.commitment = this.commitment;
5491
+ }
5492
+
5493
+ const args = [encodedTransaction, config];
5494
+ const unsafeRes = await this._rpcRequest('simulateTransaction', args);
5495
+ const res = create(unsafeRes, SimulatedTransactionResponseStruct);
5496
+ if ('error' in res) {
5497
+ throw new Error('failed to simulate transaction: ' + res.error.message);
5498
+ }
5499
+ return res.result;
5500
+ }
5501
+
3942
5502
  let transaction;
3943
5503
  if (transactionOrMessage instanceof Transaction) {
3944
5504
  let originalTx: Transaction = transactionOrMessage;
3945
- transaction = new Transaction({
3946
- recentBlockhash: originalTx.recentBlockhash,
3947
- nonceInfo: originalTx.nonceInfo,
3948
- feePayer: originalTx.feePayer,
3949
- signatures: [...originalTx.signatures],
3950
- });
5505
+ transaction = new Transaction();
5506
+ transaction.feePayer = originalTx.feePayer;
3951
5507
  transaction.instructions = transactionOrMessage.instructions;
5508
+ transaction.nonceInfo = originalTx.nonceInfo;
5509
+ transaction.signatures = originalTx.signatures;
3952
5510
  } else {
3953
5511
  transaction = Transaction.populate(transactionOrMessage);
3954
5512
  // HACK: this function relies on mutating the populated transaction
3955
5513
  transaction._message = transaction._json = undefined;
3956
5514
  }
3957
5515
 
5516
+ if (configOrSigners !== undefined && !Array.isArray(configOrSigners)) {
5517
+ throw new Error('Invalid arguments');
5518
+ }
5519
+
5520
+ const signers = configOrSigners;
3958
5521
  if (transaction.nonceInfo && signers) {
3959
5522
  transaction.sign(...signers);
3960
5523
  } else {
3961
5524
  let disableCache = this._disableBlockhashCaching;
3962
5525
  for (;;) {
3963
- transaction.recentBlockhash = await this._recentBlockhash(disableCache);
5526
+ const latestBlockhash = await this._blockhashWithExpiryBlockHeight(
5527
+ disableCache,
5528
+ );
5529
+ transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
5530
+ transaction.recentBlockhash = latestBlockhash.blockhash;
3964
5531
 
3965
5532
  if (!signers) break;
3966
5533
 
@@ -4037,18 +5604,58 @@ export class Connection {
4037
5604
 
4038
5605
  /**
4039
5606
  * Sign and send a transaction
5607
+ *
5608
+ * @deprecated Instead, call {@link sendTransaction} with a {@link
5609
+ * VersionedTransaction}
4040
5610
  */
4041
- async sendTransaction(
5611
+ sendTransaction(
4042
5612
  transaction: Transaction,
4043
5613
  signers: Array<Signer>,
4044
5614
  options?: SendOptions,
5615
+ ): Promise<TransactionSignature>;
5616
+
5617
+ /**
5618
+ * Send a signed transaction
5619
+ */
5620
+ // eslint-disable-next-line no-dupe-class-members
5621
+ sendTransaction(
5622
+ transaction: VersionedTransaction,
5623
+ options?: SendOptions,
5624
+ ): Promise<TransactionSignature>;
5625
+
5626
+ /**
5627
+ * Sign and send a transaction
5628
+ */
5629
+ // eslint-disable-next-line no-dupe-class-members
5630
+ async sendTransaction(
5631
+ transaction: VersionedTransaction | Transaction,
5632
+ signersOrOptions?: Array<Signer> | SendOptions,
5633
+ options?: SendOptions,
4045
5634
  ): Promise<TransactionSignature> {
5635
+ if ('version' in transaction) {
5636
+ if (signersOrOptions && Array.isArray(signersOrOptions)) {
5637
+ throw new Error('Invalid arguments');
5638
+ }
5639
+
5640
+ const wireTransaction = transaction.serialize();
5641
+ return await this.sendRawTransaction(wireTransaction, options);
5642
+ }
5643
+
5644
+ if (signersOrOptions === undefined || !Array.isArray(signersOrOptions)) {
5645
+ throw new Error('Invalid arguments');
5646
+ }
5647
+
5648
+ const signers = signersOrOptions;
4046
5649
  if (transaction.nonceInfo) {
4047
5650
  transaction.sign(...signers);
4048
5651
  } else {
4049
5652
  let disableCache = this._disableBlockhashCaching;
4050
5653
  for (;;) {
4051
- transaction.recentBlockhash = await this._recentBlockhash(disableCache);
5654
+ const latestBlockhash = await this._blockhashWithExpiryBlockHeight(
5655
+ disableCache,
5656
+ );
5657
+ transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
5658
+ transaction.recentBlockhash = latestBlockhash.blockhash;
4052
5659
  transaction.sign(...signers);
4053
5660
  if (!transaction.signature) {
4054
5661
  throw new Error('!signature'); // should never happen
@@ -4103,9 +5710,12 @@ export class Connection {
4103
5710
  const preflightCommitment =
4104
5711
  (options && options.preflightCommitment) || this.commitment;
4105
5712
 
4106
- if (options && options.maxRetries) {
5713
+ if (options && options.maxRetries != null) {
4107
5714
  config.maxRetries = options.maxRetries;
4108
5715
  }
5716
+ if (options && options.minContextSlot != null) {
5717
+ config.minContextSlot = options.minContextSlot;
5718
+ }
4109
5719
  if (skipPreflight) {
4110
5720
  config.skipPreflight = skipPreflight;
4111
5721
  }
@@ -4145,6 +5755,7 @@ export class Connection {
4145
5755
  * @internal
4146
5756
  */
4147
5757
  _wsOnError(err: Error) {
5758
+ this._rpcWebSocketConnected = false;
4148
5759
  console.error('ws error:', err.message);
4149
5760
  }
4150
5761
 
@@ -4152,6 +5763,13 @@ export class Connection {
4152
5763
  * @internal
4153
5764
  */
4154
5765
  _wsOnClose(code: number) {
5766
+ this._rpcWebSocketConnected = false;
5767
+ this._rpcWebSocketGeneration =
5768
+ (this._rpcWebSocketGeneration + 1) % Number.MAX_SAFE_INTEGER;
5769
+ if (this._rpcWebSocketIdleTimeout) {
5770
+ clearTimeout(this._rpcWebSocketIdleTimeout);
5771
+ this._rpcWebSocketIdleTimeout = null;
5772
+ }
4155
5773
  if (this._rpcWebSocketHeartbeat) {
4156
5774
  clearInterval(this._rpcWebSocketHeartbeat);
4157
5775
  this._rpcWebSocketHeartbeat = null;
@@ -4168,13 +5786,60 @@ export class Connection {
4168
5786
  Object.entries(
4169
5787
  this._subscriptionsByHash as Record<SubscriptionConfigHash, Subscription>,
4170
5788
  ).forEach(([hash, subscription]) => {
4171
- this._subscriptionsByHash[hash] = {
5789
+ this._setSubscription(hash, {
4172
5790
  ...subscription,
4173
5791
  state: 'pending',
4174
- };
5792
+ });
4175
5793
  });
4176
5794
  }
4177
5795
 
5796
+ /**
5797
+ * @internal
5798
+ */
5799
+ private _setSubscription(
5800
+ hash: SubscriptionConfigHash,
5801
+ nextSubscription: Subscription,
5802
+ ) {
5803
+ const prevState = this._subscriptionsByHash[hash]?.state;
5804
+ this._subscriptionsByHash[hash] = nextSubscription;
5805
+ if (prevState !== nextSubscription.state) {
5806
+ const stateChangeCallbacks =
5807
+ this._subscriptionStateChangeCallbacksByHash[hash];
5808
+ if (stateChangeCallbacks) {
5809
+ stateChangeCallbacks.forEach(cb => {
5810
+ try {
5811
+ cb(nextSubscription.state);
5812
+ // eslint-disable-next-line no-empty
5813
+ } catch {}
5814
+ });
5815
+ }
5816
+ }
5817
+ }
5818
+
5819
+ /**
5820
+ * @internal
5821
+ */
5822
+ private _onSubscriptionStateChange(
5823
+ clientSubscriptionId: ClientSubscriptionId,
5824
+ callback: SubscriptionStateChangeCallback,
5825
+ ): SubscriptionStateChangeDisposeFn {
5826
+ const hash =
5827
+ this._subscriptionHashByClientSubscriptionId[clientSubscriptionId];
5828
+ if (hash == null) {
5829
+ return () => {};
5830
+ }
5831
+ const stateChangeCallbacks = (this._subscriptionStateChangeCallbacksByHash[
5832
+ hash
5833
+ ] ||= new Set());
5834
+ stateChangeCallbacks.add(callback);
5835
+ return () => {
5836
+ stateChangeCallbacks.delete(callback);
5837
+ if (stateChangeCallbacks.size === 0) {
5838
+ delete this._subscriptionStateChangeCallbacksByHash[hash];
5839
+ }
5840
+ };
5841
+ }
5842
+
4178
5843
  /**
4179
5844
  * @internal
4180
5845
  */
@@ -4210,13 +5875,22 @@ export class Connection {
4210
5875
  return;
4211
5876
  }
4212
5877
 
5878
+ const activeWebSocketGeneration = this._rpcWebSocketGeneration;
5879
+ const isCurrentConnectionStillActive = () => {
5880
+ return activeWebSocketGeneration === this._rpcWebSocketGeneration;
5881
+ };
5882
+
4213
5883
  await Promise.all(
4214
- Object.entries(
4215
- this._subscriptionsByHash as Record<
4216
- SubscriptionConfigHash,
4217
- Subscription
4218
- >,
4219
- ).map(async ([hash, subscription]) => {
5884
+ // Don't be tempted to change this to `Object.entries`. We call
5885
+ // `_updateSubscriptions` recursively when processing the state,
5886
+ // so it's important that we look up the *current* version of
5887
+ // each subscription, every time we process a hash.
5888
+ Object.keys(this._subscriptionsByHash).map(async hash => {
5889
+ const subscription = this._subscriptionsByHash[hash];
5890
+ if (subscription === undefined) {
5891
+ // This entry has since been deleted. Skip.
5892
+ return;
5893
+ }
4220
5894
  switch (subscription.state) {
4221
5895
  case 'pending':
4222
5896
  case 'unsubscribed':
@@ -4244,62 +5918,23 @@ export class Connection {
4244
5918
  return;
4245
5919
  }
4246
5920
  await (async () => {
4247
- const {method, params} = subscription;
4248
- let args: IWSRequestParams;
4249
- switch (method) {
4250
- case 'accountSubscribe':
4251
- args = this._buildArgs(
4252
- [params.publicKey],
4253
- params.commitment,
4254
- 'base64',
4255
- );
4256
- break;
4257
- case 'logsSubscribe':
4258
- args = this._buildArgs(
4259
- [
4260
- typeof params.filter === 'object'
4261
- ? {mentions: [params.filter.toString()]}
4262
- : params.filter,
4263
- ],
4264
- params.commitment,
4265
- );
4266
- break;
4267
- case 'programSubscribe':
4268
- args = this._buildArgs(
4269
- [params.programId],
4270
- params.commitment,
4271
- 'base64',
4272
- params.filters
4273
- ? {
4274
- filters: params.filters,
4275
- }
4276
- : undefined,
4277
- );
4278
- break;
4279
- case 'signatureSubscribe':
4280
- args = [params.signature, params.options].filter(Boolean);
4281
- break;
4282
- case 'rootSubscribe':
4283
- case 'slotSubscribe':
4284
- case 'slotsUpdatesSubscribe':
4285
- args = [];
4286
- break;
4287
- }
5921
+ const {args, method} = subscription;
4288
5922
  try {
4289
- this._subscriptionsByHash[hash] = {
5923
+ this._setSubscription(hash, {
4290
5924
  ...subscription,
4291
5925
  state: 'subscribing',
4292
- };
5926
+ });
4293
5927
  const serverSubscriptionId: ServerSubscriptionId =
4294
5928
  (await this._rpcWebSocket.call(method, args)) as number;
4295
- this._subscriptionsByHash[hash] = {
5929
+ this._setSubscription(hash, {
4296
5930
  ...subscription,
4297
5931
  serverSubscriptionId,
4298
5932
  state: 'subscribed',
4299
- };
5933
+ });
4300
5934
  this._subscriptionCallbacksByServerSubscriptionId[
4301
5935
  serverSubscriptionId
4302
5936
  ] = subscription.callbacks;
5937
+ await this._updateSubscriptions();
4303
5938
  } catch (e) {
4304
5939
  if (e instanceof Error) {
4305
5940
  console.error(
@@ -4308,12 +5943,14 @@ export class Connection {
4308
5943
  e.message,
4309
5944
  );
4310
5945
  }
5946
+ if (!isCurrentConnectionStillActive()) {
5947
+ return;
5948
+ }
4311
5949
  // TODO: Maybe add an 'errored' state or a retry limit?
4312
- this._subscriptionsByHash[hash] = {
5950
+ this._setSubscription(hash, {
4313
5951
  ...subscription,
4314
5952
  state: 'pending',
4315
- };
4316
- } finally {
5953
+ });
4317
5954
  await this._updateSubscriptions();
4318
5955
  }
4319
5956
  })();
@@ -4341,10 +5978,14 @@ export class Connection {
4341
5978
  serverSubscriptionId,
4342
5979
  );
4343
5980
  } else {
4344
- this._subscriptionsByHash[hash] = {
5981
+ this._setSubscription(hash, {
5982
+ ...subscription,
5983
+ state: 'unsubscribing',
5984
+ });
5985
+ this._setSubscription(hash, {
4345
5986
  ...subscription,
4346
5987
  state: 'unsubscribing',
4347
- };
5988
+ });
4348
5989
  try {
4349
5990
  await this._rpcWebSocket.call(unsubscribeMethod, [
4350
5991
  serverSubscriptionId,
@@ -4353,19 +5994,22 @@ export class Connection {
4353
5994
  if (e instanceof Error) {
4354
5995
  console.error(`${unsubscribeMethod} error:`, e.message);
4355
5996
  }
5997
+ if (!isCurrentConnectionStillActive()) {
5998
+ return;
5999
+ }
4356
6000
  // TODO: Maybe add an 'errored' state or a retry limit?
4357
- this._subscriptionsByHash[hash] = {
6001
+ this._setSubscription(hash, {
4358
6002
  ...subscription,
4359
6003
  state: 'subscribed',
4360
- };
6004
+ });
4361
6005
  await this._updateSubscriptions();
4362
6006
  return;
4363
6007
  }
4364
6008
  }
4365
- this._subscriptionsByHash[hash] = {
6009
+ this._setSubscription(hash, {
4366
6010
  ...subscription,
4367
6011
  state: 'unsubscribed',
4368
- };
6012
+ });
4369
6013
  await this._updateSubscriptions();
4370
6014
  })();
4371
6015
  }
@@ -4379,21 +6023,28 @@ export class Connection {
4379
6023
  }
4380
6024
 
4381
6025
  /**
4382
- *
4383
6026
  * @internal
4384
6027
  */
4385
- _publishNotification<TCallback extends SubscriptionConfig['callback']>(
6028
+ private _handleServerNotification<
6029
+ TCallback extends SubscriptionConfig['callback'],
6030
+ >(
4386
6031
  serverSubscriptionId: ServerSubscriptionId,
4387
- publishArgs: Parameters<TCallback>,
6032
+ callbackArgs: Parameters<TCallback>,
4388
6033
  ): void {
4389
6034
  const callbacks =
4390
6035
  this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
4391
- if (callbacks == null) {
6036
+ if (callbacks === undefined) {
4392
6037
  return;
4393
6038
  }
4394
6039
  callbacks.forEach(cb => {
4395
6040
  try {
4396
- cb(...(publishArgs as [any, any]));
6041
+ cb(
6042
+ // I failed to find a way to convince TypeScript that `cb` is of type
6043
+ // `TCallback` which is certainly compatible with `Parameters<TCallback>`.
6044
+ // See https://github.com/microsoft/TypeScript/issues/47615
6045
+ // @ts-ignore
6046
+ ...callbackArgs,
6047
+ );
4397
6048
  } catch (e) {
4398
6049
  console.error(e);
4399
6050
  }
@@ -4408,85 +6059,70 @@ export class Connection {
4408
6059
  notification,
4409
6060
  AccountNotificationResult,
4410
6061
  );
4411
- this._publishNotification<AccountChangeCallback>(subscription, [
6062
+ this._handleServerNotification<AccountChangeCallback>(subscription, [
4412
6063
  result.value,
4413
6064
  result.context,
4414
6065
  ]);
4415
6066
  }
4416
6067
 
4417
6068
  /**
4418
- * @private
6069
+ * @internal
4419
6070
  */
4420
- _makeSubscription(
4421
- // The subscription config, before applying defaults
4422
- rawConfig: SubscriptionConfig,
6071
+ private _makeSubscription(
6072
+ subscriptionConfig: SubscriptionConfig,
6073
+ /**
6074
+ * When preparing `args` for a call to `_makeSubscription`, be sure
6075
+ * to carefully apply a default `commitment` property, if necessary.
6076
+ *
6077
+ * - If the user supplied a `commitment` use that.
6078
+ * - Otherwise, if the `Connection::commitment` is set, use that.
6079
+ * - Otherwise, set it to the RPC server default: `finalized`.
6080
+ *
6081
+ * This is extremely important to ensure that these two fundamentally
6082
+ * identical subscriptions produce the same identifying hash:
6083
+ *
6084
+ * - A subscription made without specifying a commitment.
6085
+ * - A subscription made where the commitment specified is the same
6086
+ * as the default applied to the subscription above.
6087
+ *
6088
+ * Example; these two subscriptions must produce the same hash:
6089
+ *
6090
+ * - An `accountSubscribe` subscription for `'PUBKEY'`
6091
+ * - An `accountSubscribe` subscription for `'PUBKEY'` with commitment
6092
+ * `'finalized'`.
6093
+ *
6094
+ * See the 'making a subscription with defaulted params omitted' test
6095
+ * in `connection-subscriptions.ts` for more.
6096
+ */
6097
+ args: IWSRequestParams,
4423
6098
  ): ClientSubscriptionId {
4424
6099
  const clientSubscriptionId = this._nextClientSubscriptionId++;
4425
- let subscriptionConfig: SubscriptionConfig;
4426
- // Apply defaults.
4427
- switch (rawConfig.method) {
4428
- case 'accountSubscribe':
4429
- case 'logsSubscribe':
4430
- case 'programSubscribe':
4431
- if (rawConfig.params.commitment === undefined) {
4432
- subscriptionConfig = {
4433
- ...rawConfig,
4434
- params: {
4435
- ...rawConfig.params,
4436
- commitment: 'finalized',
4437
- },
4438
- } as SubscriptionConfig;
4439
- } else {
4440
- subscriptionConfig = rawConfig;
4441
- }
4442
- break;
4443
- case 'signatureSubscribe':
4444
- if (
4445
- rawConfig.params.options &&
4446
- rawConfig.params.options.commitment === undefined
4447
- ) {
4448
- subscriptionConfig = {
4449
- ...rawConfig,
4450
- params: {
4451
- ...rawConfig.params,
4452
- options: {
4453
- ...rawConfig.params.options,
4454
- commitment: 'finalized',
4455
- },
4456
- },
4457
- };
4458
- } else {
4459
- subscriptionConfig = rawConfig;
4460
- }
4461
- break;
4462
- default:
4463
- subscriptionConfig = rawConfig;
4464
- }
4465
- const hash = hashSubscriptionConfig(subscriptionConfig);
6100
+ const hash = fastStableStringify(
6101
+ [subscriptionConfig.method, args],
6102
+ true /* isArrayProp */,
6103
+ );
4466
6104
  const existingSubscription = this._subscriptionsByHash[hash];
4467
- if (existingSubscription == null) {
6105
+ if (existingSubscription === undefined) {
4468
6106
  this._subscriptionsByHash[hash] = {
4469
6107
  ...subscriptionConfig,
6108
+ args,
4470
6109
  callbacks: new Set([subscriptionConfig.callback]),
4471
6110
  state: 'pending',
4472
6111
  };
4473
6112
  } else {
4474
6113
  existingSubscription.callbacks.add(subscriptionConfig.callback);
4475
6114
  }
4476
- let unsubscribed = false;
6115
+ this._subscriptionHashByClientSubscriptionId[clientSubscriptionId] = hash;
4477
6116
  this._subscriptionDisposeFunctionsByClientSubscriptionId[
4478
6117
  clientSubscriptionId
4479
6118
  ] = async () => {
4480
- if (unsubscribed) {
4481
- return;
4482
- }
4483
- unsubscribed = true;
4484
6119
  delete this._subscriptionDisposeFunctionsByClientSubscriptionId[
4485
6120
  clientSubscriptionId
4486
6121
  ];
6122
+ delete this._subscriptionHashByClientSubscriptionId[clientSubscriptionId];
4487
6123
  const subscription = this._subscriptionsByHash[hash];
4488
6124
  assert(
4489
- subscription,
6125
+ subscription !== undefined,
4490
6126
  `Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`,
4491
6127
  );
4492
6128
  subscription.callbacks.delete(subscriptionConfig.callback);
@@ -4509,15 +6145,19 @@ export class Connection {
4509
6145
  callback: AccountChangeCallback,
4510
6146
  commitment?: Commitment,
4511
6147
  ): ClientSubscriptionId {
4512
- return this._makeSubscription({
4513
- callback,
4514
- method: 'accountSubscribe',
4515
- params: {
4516
- commitment,
4517
- publicKey: publicKey.toBase58(),
6148
+ const args = this._buildArgs(
6149
+ [publicKey.toBase58()],
6150
+ commitment || this._commitment || 'finalized', // Apply connection/server default.
6151
+ 'base64',
6152
+ );
6153
+ return this._makeSubscription(
6154
+ {
6155
+ callback,
6156
+ method: 'accountSubscribe',
6157
+ unsubscribeMethod: 'accountUnsubscribe',
4518
6158
  },
4519
- unsubscribeMethod: 'accountUnsubscribe',
4520
- });
6159
+ args,
6160
+ );
4521
6161
  }
4522
6162
 
4523
6163
  /**
@@ -4542,7 +6182,7 @@ export class Connection {
4542
6182
  notification,
4543
6183
  ProgramAccountNotificationResult,
4544
6184
  );
4545
- this._publishNotification<ProgramAccountChangeCallback>(subscription, [
6185
+ this._handleServerNotification<ProgramAccountChangeCallback>(subscription, [
4546
6186
  {
4547
6187
  accountId: result.value.pubkey,
4548
6188
  accountInfo: result.value.account,
@@ -4567,16 +6207,20 @@ export class Connection {
4567
6207
  commitment?: Commitment,
4568
6208
  filters?: GetProgramAccountsFilter[],
4569
6209
  ): ClientSubscriptionId {
4570
- return this._makeSubscription({
4571
- callback,
4572
- method: 'programSubscribe',
4573
- params: {
4574
- commitment,
4575
- filters,
4576
- programId: programId.toBase58(),
6210
+ const args = this._buildArgs(
6211
+ [programId.toBase58()],
6212
+ commitment || this._commitment || 'finalized', // Apply connection/server default.
6213
+ 'base64' /* encoding */,
6214
+ filters ? {filters: filters} : undefined /* extra */,
6215
+ );
6216
+ return this._makeSubscription(
6217
+ {
6218
+ callback,
6219
+ method: 'programSubscribe',
6220
+ unsubscribeMethod: 'programUnsubscribe',
4577
6221
  },
4578
- unsubscribeMethod: 'programUnsubscribe',
4579
- });
6222
+ args,
6223
+ );
4580
6224
  }
4581
6225
 
4582
6226
  /**
@@ -4601,15 +6245,18 @@ export class Connection {
4601
6245
  callback: LogsCallback,
4602
6246
  commitment?: Commitment,
4603
6247
  ): ClientSubscriptionId {
4604
- return this._makeSubscription({
4605
- callback,
4606
- method: 'logsSubscribe',
4607
- params: {
4608
- commitment,
4609
- filter,
6248
+ const args = this._buildArgs(
6249
+ [typeof filter === 'object' ? {mentions: [filter.toString()]} : filter],
6250
+ commitment || this._commitment || 'finalized', // Apply connection/server default.
6251
+ );
6252
+ return this._makeSubscription(
6253
+ {
6254
+ callback,
6255
+ method: 'logsSubscribe',
6256
+ unsubscribeMethod: 'logsUnsubscribe',
4610
6257
  },
4611
- unsubscribeMethod: 'logsUnsubscribe',
4612
- });
6258
+ args,
6259
+ );
4613
6260
  }
4614
6261
 
4615
6262
  /**
@@ -4628,7 +6275,7 @@ export class Connection {
4628
6275
  */
4629
6276
  _wsOnLogsNotification(notification: Object) {
4630
6277
  const {result, subscription} = create(notification, LogsNotificationResult);
4631
- this._publishNotification<LogsCallback>(subscription, [
6278
+ this._handleServerNotification<LogsCallback>(subscription, [
4632
6279
  result.value,
4633
6280
  result.context,
4634
6281
  ]);
@@ -4639,7 +6286,7 @@ export class Connection {
4639
6286
  */
4640
6287
  _wsOnSlotNotification(notification: Object) {
4641
6288
  const {result, subscription} = create(notification, SlotNotificationResult);
4642
- this._publishNotification<SlotChangeCallback>(subscription, [result]);
6289
+ this._handleServerNotification<SlotChangeCallback>(subscription, [result]);
4643
6290
  }
4644
6291
 
4645
6292
  /**
@@ -4649,12 +6296,14 @@ export class Connection {
4649
6296
  * @return subscription id
4650
6297
  */
4651
6298
  onSlotChange(callback: SlotChangeCallback): ClientSubscriptionId {
4652
- return this._makeSubscription({
4653
- callback,
4654
- method: 'slotSubscribe',
4655
- params: undefined,
4656
- unsubscribeMethod: 'slotUnsubscribe',
4657
- });
6299
+ return this._makeSubscription(
6300
+ {
6301
+ callback,
6302
+ method: 'slotSubscribe',
6303
+ unsubscribeMethod: 'slotUnsubscribe',
6304
+ },
6305
+ [] /* args */,
6306
+ );
4658
6307
  }
4659
6308
 
4660
6309
  /**
@@ -4679,7 +6328,7 @@ export class Connection {
4679
6328
  notification,
4680
6329
  SlotUpdateNotificationResult,
4681
6330
  );
4682
- this._publishNotification<SlotUpdateCallback>(subscription, [result]);
6331
+ this._handleServerNotification<SlotUpdateCallback>(subscription, [result]);
4683
6332
  }
4684
6333
 
4685
6334
  /**
@@ -4690,12 +6339,14 @@ export class Connection {
4690
6339
  * @return subscription id
4691
6340
  */
4692
6341
  onSlotUpdate(callback: SlotUpdateCallback): ClientSubscriptionId {
4693
- return this._makeSubscription({
4694
- callback,
4695
- method: 'slotsUpdatesSubscribe',
4696
- params: undefined,
4697
- unsubscribeMethod: 'slotsUpdatesUnsubscribe',
4698
- });
6342
+ return this._makeSubscription(
6343
+ {
6344
+ callback,
6345
+ method: 'slotsUpdatesSubscribe',
6346
+ unsubscribeMethod: 'slotsUpdatesUnsubscribe',
6347
+ },
6348
+ [] /* args */,
6349
+ );
4699
6350
  }
4700
6351
 
4701
6352
  /**
@@ -4716,7 +6367,7 @@ export class Connection {
4716
6367
  * @internal
4717
6368
  */
4718
6369
 
4719
- async _unsubscribeClientSubscription(
6370
+ private async _unsubscribeClientSubscription(
4720
6371
  clientSubscriptionId: ClientSubscriptionId,
4721
6372
  subscriptionName: string,
4722
6373
  ) {
@@ -4727,8 +6378,10 @@ export class Connection {
4727
6378
  if (dispose) {
4728
6379
  await dispose();
4729
6380
  } else {
4730
- throw new Error(
4731
- `Unknown ${subscriptionName} client subscription id: ${clientSubscriptionId}`,
6381
+ console.warn(
6382
+ 'Ignored unsubscribe request because an active subscription with id ' +
6383
+ `\`${clientSubscriptionId}\` for '${subscriptionName}' events ` +
6384
+ 'could not be found.',
4732
6385
  );
4733
6386
  }
4734
6387
  }
@@ -4800,7 +6453,7 @@ export class Connection {
4800
6453
  */
4801
6454
  this._subscriptionsAutoDisposedByRpc.add(subscription);
4802
6455
  }
4803
- this._publishNotification<SignatureSubscriptionCallback>(
6456
+ this._handleServerNotification<SignatureSubscriptionCallback>(
4804
6457
  subscription,
4805
6458
  result.value === 'receivedSignature'
4806
6459
  ? [{type: 'received'}, result.context]
@@ -4821,27 +6474,30 @@ export class Connection {
4821
6474
  callback: SignatureResultCallback,
4822
6475
  commitment?: Commitment,
4823
6476
  ): ClientSubscriptionId {
4824
- const clientSubscriptionId = this._makeSubscription({
4825
- callback: (notification, context) => {
4826
- if (notification.type === 'status') {
4827
- callback(notification.result, context);
4828
- // Signatures subscriptions are auto-removed by the RPC service
4829
- // so no need to explicitly send an unsubscribe message.
4830
- try {
4831
- this.removeSignatureListener(clientSubscriptionId);
4832
- // eslint-disable-next-line no-empty
4833
- } catch {
4834
- // Already removed.
6477
+ const args = this._buildArgs(
6478
+ [signature],
6479
+ commitment || this._commitment || 'finalized', // Apply connection/server default.
6480
+ );
6481
+ const clientSubscriptionId = this._makeSubscription(
6482
+ {
6483
+ callback: (notification, context) => {
6484
+ if (notification.type === 'status') {
6485
+ callback(notification.result, context);
6486
+ // Signatures subscriptions are auto-removed by the RPC service
6487
+ // so no need to explicitly send an unsubscribe message.
6488
+ try {
6489
+ this.removeSignatureListener(clientSubscriptionId);
6490
+ // eslint-disable-next-line no-empty
6491
+ } catch (_err) {
6492
+ // Already removed.
6493
+ }
4835
6494
  }
4836
- }
4837
- },
4838
- method: 'signatureSubscribe',
4839
- params: {
4840
- options: {commitment},
4841
- signature,
6495
+ },
6496
+ method: 'signatureSubscribe',
6497
+ unsubscribeMethod: 'signatureUnsubscribe',
4842
6498
  },
4843
- unsubscribeMethod: 'signatureUnsubscribe',
4844
- });
6499
+ args,
6500
+ );
4845
6501
  return clientSubscriptionId;
4846
6502
  }
4847
6503
 
@@ -4860,25 +6516,35 @@ export class Connection {
4860
6516
  callback: SignatureSubscriptionCallback,
4861
6517
  options?: SignatureSubscriptionOptions,
4862
6518
  ): ClientSubscriptionId {
4863
- const clientSubscriptionId = this._makeSubscription({
4864
- callback: (notification, context) => {
4865
- callback(notification, context);
4866
- // Signatures subscriptions are auto-removed by the RPC service
4867
- // so no need to explicitly send an unsubscribe message.
4868
- try {
4869
- this.removeSignatureListener(clientSubscriptionId);
4870
- // eslint-disable-next-line no-empty
4871
- } catch {
4872
- // Already removed.
4873
- }
4874
- },
4875
- method: 'signatureSubscribe',
4876
- params: {
4877
- options,
4878
- signature,
6519
+ const {commitment, ...extra} = {
6520
+ ...options,
6521
+ commitment:
6522
+ (options && options.commitment) || this._commitment || 'finalized', // Apply connection/server default.
6523
+ };
6524
+ const args = this._buildArgs(
6525
+ [signature],
6526
+ commitment,
6527
+ undefined /* encoding */,
6528
+ extra,
6529
+ );
6530
+ const clientSubscriptionId = this._makeSubscription(
6531
+ {
6532
+ callback: (notification, context) => {
6533
+ callback(notification, context);
6534
+ // Signatures subscriptions are auto-removed by the RPC service
6535
+ // so no need to explicitly send an unsubscribe message.
6536
+ try {
6537
+ this.removeSignatureListener(clientSubscriptionId);
6538
+ // eslint-disable-next-line no-empty
6539
+ } catch (_err) {
6540
+ // Already removed.
6541
+ }
6542
+ },
6543
+ method: 'signatureSubscribe',
6544
+ unsubscribeMethod: 'signatureUnsubscribe',
4879
6545
  },
4880
- unsubscribeMethod: 'signatureUnsubscribe',
4881
- });
6546
+ args,
6547
+ );
4882
6548
  return clientSubscriptionId;
4883
6549
  }
4884
6550
 
@@ -4901,7 +6567,7 @@ export class Connection {
4901
6567
  */
4902
6568
  _wsOnRootNotification(notification: Object) {
4903
6569
  const {result, subscription} = create(notification, RootNotificationResult);
4904
- this._publishNotification<RootChangeCallback>(subscription, [result]);
6570
+ this._handleServerNotification<RootChangeCallback>(subscription, [result]);
4905
6571
  }
4906
6572
 
4907
6573
  /**
@@ -4911,12 +6577,14 @@ export class Connection {
4911
6577
  * @return subscription id
4912
6578
  */
4913
6579
  onRootChange(callback: RootChangeCallback): ClientSubscriptionId {
4914
- return this._makeSubscription({
4915
- callback,
4916
- method: 'rootSubscribe',
4917
- params: undefined,
4918
- unsubscribeMethod: 'rootUnsubscribe',
4919
- });
6580
+ return this._makeSubscription(
6581
+ {
6582
+ callback,
6583
+ method: 'rootSubscribe',
6584
+ unsubscribeMethod: 'rootUnsubscribe',
6585
+ },
6586
+ [] /* args */,
6587
+ );
4920
6588
  }
4921
6589
 
4922
6590
  /**