@solana/web3.js 1.68.1 → 1.70.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana/web3.js",
3
- "version": "1.68.1",
3
+ "version": "1.70.0-pr-29130",
4
4
  "description": "Solana Javascript API",
5
5
  "keywords": [
6
6
  "api",
@@ -51,7 +51,7 @@
51
51
  "pretty": "prettier --check '{,{src,test}/**/}*.{j,t}s'",
52
52
  "pretty:fix": "prettier --write '{,{src,test}/**/}*.{j,t}s'",
53
53
  "re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git",
54
- "test": "cross-env TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\", \"target\": \"es2019\" }' ts-mocha --require esm './test/**/*.test.ts'",
54
+ "test": "cross-env NODE_ENV=test TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\", \"target\": \"es2019\" }' ts-mocha --require esm './test/**/*.test.ts'",
55
55
  "test:cover": "nyc --reporter=lcov npm run test",
56
56
  "test:live": "TEST_LIVE=1 npm run test",
57
57
  "test:live-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:live"
@@ -62,6 +62,7 @@
62
62
  "@noble/hashes": "^1.1.2",
63
63
  "@noble/secp256k1": "^1.6.3",
64
64
  "@solana/buffer-layout": "^4.0.0",
65
+ "agentkeepalive": "^4.2.1",
65
66
  "bigint-buffer": "^1.1.5",
66
67
  "bn.js": "^5.0.0",
67
68
  "borsh": "^0.7.0",
@@ -95,7 +96,7 @@
95
96
  "@types/express-serve-static-core": "^4.17.21",
96
97
  "@types/mocha": "^10.0.0",
97
98
  "@types/mz": "^2.7.3",
98
- "@types/node": "^17.0.24",
99
+ "@types/node": "^18.11.10",
99
100
  "@types/node-fetch": "2",
100
101
  "@types/sinon": "^10.0.0",
101
102
  "@types/sinon-chai": "^3.2.8",
@@ -114,6 +115,7 @@
114
115
  "mocha": "^10.1.0",
115
116
  "mockttp": "^2.0.1",
116
117
  "mz": "^2.7.0",
118
+ "node-abort-controller": "^3.0.1",
117
119
  "npm-run-all": "^4.1.5",
118
120
  "nyc": "^15.1.0",
119
121
  "prettier": "^2.3.0",
@@ -129,8 +131,8 @@
129
131
  "ts-mocha": "^10.0.0",
130
132
  "ts-node": "^10.0.0",
131
133
  "tslib": "^2.1.0",
132
- "typedoc": "^0.22.2",
133
- "typescript": "^4.3.2"
134
+ "typedoc": "^0.23",
135
+ "typescript": "^4.9"
134
136
  },
135
137
  "engines": {
136
138
  "node": ">=12.20.0"
package/src/connection.ts CHANGED
@@ -1,7 +1,10 @@
1
+ import Agent from 'agentkeepalive';
1
2
  import bs58 from 'bs58';
2
3
  import {Buffer} from 'buffer';
3
4
  // @ts-ignore
4
5
  import fastStableStringify from 'fast-stable-stringify';
6
+ import type {Agent as HttpAgent} from 'http';
7
+ import {Agent as HttpsAgent} from 'https';
5
8
  import {
6
9
  type as pick,
7
10
  number,
@@ -25,7 +28,6 @@ import {Client as RpcWebSocketClient} from 'rpc-websockets';
25
28
  import RpcClient from 'jayson/lib/client/browser';
26
29
  import {JSONRPCError} from 'jayson';
27
30
 
28
- import {AgentManager} from './agent-manager';
29
31
  import {EpochSchedule} from './epoch-schedule';
30
32
  import {SendTransactionError, SolanaJSONRPCError} from './errors';
31
33
  import fetchImpl, {Response} from './fetch-impl';
@@ -311,9 +313,39 @@ export type BlockhashWithExpiryBlockHeight = Readonly<{
311
313
  * A strategy for confirming transactions that uses the last valid
312
314
  * block height for a given blockhash to check for transaction expiration.
313
315
  */
314
- export type BlockheightBasedTransactionConfirmationStrategy = {
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;
315
347
  signature: TransactionSignature;
316
- } & BlockhashWithExpiryBlockHeight;
348
+ }>;
317
349
 
318
350
  /* @internal */
319
351
  function assertEndpointUrl(putativeUrl: string) {
@@ -340,28 +372,6 @@ function extractCommitmentFromConfig<TConfig>(
340
372
  return {commitment, config};
341
373
  }
342
374
 
343
- /**
344
- * A strategy for confirming durable nonce transactions.
345
- */
346
- export type DurableNonceTransactionConfirmationStrategy = {
347
- /**
348
- * The lowest slot at which to fetch the nonce value from the
349
- * nonce account. This should be no lower than the slot at
350
- * which the last-known value of the nonce was fetched.
351
- */
352
- minContextSlot: number;
353
- /**
354
- * The account where the current value of the nonce is stored.
355
- */
356
- nonceAccountPubkey: PublicKey;
357
- /**
358
- * The nonce value that was used to sign the transaction
359
- * for which confirmation is being sought.
360
- */
361
- nonceValue: DurableNonce;
362
- signature: TransactionSignature;
363
- };
364
-
365
375
  /**
366
376
  * @internal
367
377
  */
@@ -1442,11 +1452,49 @@ function createRpcClient(
1442
1452
  customFetch?: FetchFn,
1443
1453
  fetchMiddleware?: FetchMiddleware,
1444
1454
  disableRetryOnRateLimit?: boolean,
1455
+ httpAgent?: HttpAgent | HttpsAgent | false,
1445
1456
  ): RpcClient {
1446
1457
  const fetch = customFetch ? customFetch : fetchImpl;
1447
- let agentManager: AgentManager | undefined;
1448
- if (!process.env.BROWSER) {
1449
- agentManager = new AgentManager(url.startsWith('https:') /* useHttps */);
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
+ }
1450
1498
  }
1451
1499
 
1452
1500
  let fetchWithMiddleware: FetchFn | undefined;
@@ -1469,7 +1517,6 @@ function createRpcClient(
1469
1517
  }
1470
1518
 
1471
1519
  const clientBrowser = new RpcClient(async (request, callback) => {
1472
- const agent = agentManager ? agentManager.requestStart() : undefined;
1473
1520
  const options = {
1474
1521
  method: 'POST',
1475
1522
  body: request,
@@ -1519,8 +1566,6 @@ function createRpcClient(
1519
1566
  }
1520
1567
  } catch (err) {
1521
1568
  if (err instanceof Error) callback(err);
1522
- } finally {
1523
- agentManager && agentManager.requestEnd();
1524
1569
  }
1525
1570
  }, {});
1526
1571
 
@@ -2847,6 +2892,12 @@ export type FetchMiddleware = (
2847
2892
  * Configuration for instantiating a Connection
2848
2893
  */
2849
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;
2850
2901
  /** Optional commitment level */
2851
2902
  commitment?: Commitment;
2852
2903
  /** Optional endpoint URL to the fullnode JSON RPC PubSub WebSocket Endpoint */
@@ -2964,6 +3015,7 @@ export class Connection {
2964
3015
  let fetch;
2965
3016
  let fetchMiddleware;
2966
3017
  let disableRetryOnRateLimit;
3018
+ let httpAgent;
2967
3019
  if (commitmentOrConfig && typeof commitmentOrConfig === 'string') {
2968
3020
  this._commitment = commitmentOrConfig;
2969
3021
  } else if (commitmentOrConfig) {
@@ -2975,6 +3027,7 @@ export class Connection {
2975
3027
  fetch = commitmentOrConfig.fetch;
2976
3028
  fetchMiddleware = commitmentOrConfig.fetchMiddleware;
2977
3029
  disableRetryOnRateLimit = commitmentOrConfig.disableRetryOnRateLimit;
3030
+ httpAgent = commitmentOrConfig.httpAgent;
2978
3031
  }
2979
3032
 
2980
3033
  this._rpcEndpoint = assertEndpointUrl(endpoint);
@@ -2986,6 +3039,7 @@ export class Connection {
2986
3039
  fetch,
2987
3040
  fetchMiddleware,
2988
3041
  disableRetryOnRateLimit,
3042
+ httpAgent,
2989
3043
  );
2990
3044
  this._rpcRequest = createRpcRequest(this._rpcClient);
2991
3045
  this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient);
@@ -3571,6 +3625,9 @@ export class Connection {
3571
3625
  const config = strategy as
3572
3626
  | BlockheightBasedTransactionConfirmationStrategy
3573
3627
  | DurableNonceTransactionConfirmationStrategy;
3628
+ if (config.abortSignal?.aborted) {
3629
+ return Promise.reject(config.abortSignal.reason);
3630
+ }
3574
3631
  rawSignature = config.signature;
3575
3632
  }
3576
3633
 
@@ -3602,6 +3659,21 @@ export class Connection {
3602
3659
  }
3603
3660
  }
3604
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
+ }
3676
+
3605
3677
  private getTransactionConfirmationPromise({
3606
3678
  commitment,
3607
3679
  signature,
@@ -3722,7 +3794,7 @@ export class Connection {
3722
3794
 
3723
3795
  private async confirmTransactionUsingBlockHeightExceedanceStrategy({
3724
3796
  commitment,
3725
- strategy: {lastValidBlockHeight, signature},
3797
+ strategy: {abortSignal, lastValidBlockHeight, signature},
3726
3798
  }: {
3727
3799
  commitment?: Commitment;
3728
3800
  strategy: BlockheightBasedTransactionConfirmationStrategy;
@@ -3753,9 +3825,14 @@ export class Connection {
3753
3825
  });
3754
3826
  const {abortConfirmation, confirmationPromise} =
3755
3827
  this.getTransactionConfirmationPromise({commitment, signature});
3828
+ const cancellationPromise = this.getCancellationPromise(abortSignal);
3756
3829
  let result: RpcResponseAndContext<SignatureResult>;
3757
3830
  try {
3758
- const outcome = await Promise.race([confirmationPromise, expiryPromise]);
3831
+ const outcome = await Promise.race([
3832
+ cancellationPromise,
3833
+ confirmationPromise,
3834
+ expiryPromise,
3835
+ ]);
3759
3836
  if (outcome.__type === TransactionStatus.PROCESSED) {
3760
3837
  result = outcome.response;
3761
3838
  } else {
@@ -3770,7 +3847,13 @@ export class Connection {
3770
3847
 
3771
3848
  private async confirmTransactionUsingDurableNonceStrategy({
3772
3849
  commitment,
3773
- strategy: {minContextSlot, nonceAccountPubkey, nonceValue, signature},
3850
+ strategy: {
3851
+ abortSignal,
3852
+ minContextSlot,
3853
+ nonceAccountPubkey,
3854
+ nonceValue,
3855
+ signature,
3856
+ },
3774
3857
  }: {
3775
3858
  commitment?: Commitment;
3776
3859
  strategy: DurableNonceTransactionConfirmationStrategy;
@@ -3821,9 +3904,14 @@ export class Connection {
3821
3904
  });
3822
3905
  const {abortConfirmation, confirmationPromise} =
3823
3906
  this.getTransactionConfirmationPromise({commitment, signature});
3907
+ const cancellationPromise = this.getCancellationPromise(abortSignal);
3824
3908
  let result: RpcResponseAndContext<SignatureResult>;
3825
3909
  try {
3826
- const outcome = await Promise.race([confirmationPromise, expiryPromise]);
3910
+ const outcome = await Promise.race([
3911
+ cancellationPromise,
3912
+ confirmationPromise,
3913
+ expiryPromise,
3914
+ ]);
3827
3915
  if (outcome.__type === TransactionStatus.PROCESSED) {
3828
3916
  result = outcome.response;
3829
3917
  } else {
@@ -26,7 +26,7 @@ function nextPowerOfTwo(n: number) {
26
26
  /**
27
27
  * Epoch schedule
28
28
  * (see https://docs.solana.com/terminology#epoch)
29
- * Can be retrieved with the {@link connection.getEpochSchedule} method
29
+ * Can be retrieved with the {@link Connection.getEpochSchedule} method
30
30
  */
31
31
  export class EpochSchedule {
32
32
  /** The maximum number of slots in each epoch */
@@ -19,7 +19,11 @@ export async function sendAndConfirmTransaction(
19
19
  connection: Connection,
20
20
  transaction: Transaction,
21
21
  signers: Array<Signer>,
22
- options?: ConfirmOptions,
22
+ options?: ConfirmOptions &
23
+ Readonly<{
24
+ // A signal that, when aborted, cancels any outstanding transaction confirmation operations
25
+ abortSignal?: AbortSignal;
26
+ }>,
23
27
  ): Promise<TransactionSignature> {
24
28
  const sendOptions = options && {
25
29
  skipPreflight: options.skipPreflight,
@@ -42,6 +46,7 @@ export async function sendAndConfirmTransaction(
42
46
  status = (
43
47
  await connection.confirmTransaction(
44
48
  {
49
+ abortSignal: options?.abortSignal,
45
50
  signature: signature,
46
51
  blockhash: transaction.recentBlockhash,
47
52
  lastValidBlockHeight: transaction.lastValidBlockHeight,
@@ -58,6 +63,7 @@ export async function sendAndConfirmTransaction(
58
63
  status = (
59
64
  await connection.confirmTransaction(
60
65
  {
66
+ abortSignal: options?.abortSignal,
61
67
  minContextSlot: transaction.minNonceContextSlot,
62
68
  nonceAccountPubkey,
63
69
  nonceValue: transaction.nonceInfo.nonce,
@@ -67,6 +73,13 @@ export async function sendAndConfirmTransaction(
67
73
  )
68
74
  ).value;
69
75
  } else {
76
+ if (options?.abortSignal != null) {
77
+ console.warn(
78
+ 'sendAndConfirmTransaction(): A transaction with a deprecated confirmation strategy was ' +
79
+ 'supplied along with an `abortSignal`. Only transactions having `lastValidBlockHeight` ' +
80
+ 'or a combination of `nonceInfo` and `minNonceContextSlot` are abortable.',
81
+ );
82
+ }
70
83
  status = (
71
84
  await connection.confirmTransaction(
72
85
  signature,
@@ -1,44 +0,0 @@
1
- import http from 'http';
2
- import https from 'https';
3
-
4
- export const DESTROY_TIMEOUT_MS = 5000;
5
-
6
- export class AgentManager {
7
- _agent: http.Agent | https.Agent;
8
- _activeRequests = 0;
9
- _destroyTimeout: ReturnType<typeof setTimeout> | null = null;
10
- _useHttps: boolean;
11
-
12
- static _newAgent(useHttps: boolean): http.Agent | https.Agent {
13
- const options = {keepAlive: true, maxSockets: 25};
14
- if (useHttps) {
15
- return new https.Agent(options);
16
- } else {
17
- return new http.Agent(options);
18
- }
19
- }
20
-
21
- constructor(useHttps?: boolean) {
22
- this._useHttps = useHttps === true;
23
- this._agent = AgentManager._newAgent(this._useHttps);
24
- }
25
-
26
- requestStart(): http.Agent | https.Agent {
27
- this._activeRequests++;
28
- if (this._destroyTimeout !== null) {
29
- clearTimeout(this._destroyTimeout);
30
- this._destroyTimeout = null;
31
- }
32
- return this._agent;
33
- }
34
-
35
- requestEnd() {
36
- this._activeRequests--;
37
- if (this._activeRequests === 0 && this._destroyTimeout === null) {
38
- this._destroyTimeout = setTimeout(() => {
39
- this._agent.destroy();
40
- this._agent = AgentManager._newAgent(this._useHttps);
41
- }, DESTROY_TIMEOUT_MS);
42
- }
43
- }
44
- }