@solana/web3.js 1.41.0 → 1.41.3

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/lib/index.cjs.js CHANGED
@@ -2120,6 +2120,16 @@ class Account {
2120
2120
 
2121
2121
  const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey('BPFLoader1111111111111111111111111111111111');
2122
2122
 
2123
+ /**
2124
+ * Maximum over-the-wire size of a Transaction
2125
+ *
2126
+ * 1280 is IPv6 minimum MTU
2127
+ * 40 bytes is the size of the IPv6 header
2128
+ * 8 bytes is the size of the fragment header
2129
+ */
2130
+ const PACKET_DATA_SIZE = 1280 - 40 - 8;
2131
+ const SIGNATURE_LENGTH_IN_BYTES = 64;
2132
+
2123
2133
  /**
2124
2134
  * Layout for a public key
2125
2135
  */
@@ -2379,20 +2389,8 @@ function assert (condition, message) {
2379
2389
 
2380
2390
  /**
2381
2391
  * Default (empty) signature
2382
- *
2383
- * Signatures are 64 bytes in length
2384
2392
  */
2385
- const DEFAULT_SIGNATURE = buffer.Buffer.alloc(64).fill(0);
2386
- /**
2387
- * Maximum over-the-wire size of a Transaction
2388
- *
2389
- * 1280 is IPv6 minimum MTU
2390
- * 40 bytes is the size of the IPv6 header
2391
- * 8 bytes is the size of the fragment header
2392
- */
2393
-
2394
- const PACKET_DATA_SIZE = 1280 - 40 - 8;
2395
- const SIGNATURE_LENGTH = 64;
2393
+ const DEFAULT_SIGNATURE = buffer.Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0);
2396
2394
  /**
2397
2395
  * Account metadata used to define instructions
2398
2396
  */
@@ -3022,8 +3020,8 @@ class Transaction {
3022
3020
  let signatures = [];
3023
3021
 
3024
3022
  for (let i = 0; i < signatureCount; i++) {
3025
- const signature = byteArray.slice(0, SIGNATURE_LENGTH);
3026
- byteArray = byteArray.slice(SIGNATURE_LENGTH);
3023
+ const signature = byteArray.slice(0, SIGNATURE_LENGTH_IN_BYTES);
3024
+ byteArray = byteArray.slice(SIGNATURE_LENGTH_IN_BYTES);
3027
3025
  signatures.push(bs58__default["default"].encode(buffer.Buffer.from(signature)));
3028
3026
  }
3029
3027
 
@@ -3912,11 +3910,11 @@ class SystemProgram {
3912
3910
  }
3913
3911
  SystemProgram.programId = new PublicKey('11111111111111111111111111111111');
3914
3912
 
3915
- // Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the
3916
3913
  // rest of the Transaction fields
3917
3914
  //
3918
3915
  // TODO: replace 300 with a proper constant for the size of the other
3919
3916
  // Transaction fields
3917
+
3920
3918
  const CHUNK_SIZE = PACKET_DATA_SIZE - 300;
3921
3919
  /**
3922
3920
  * Program loader interface
@@ -4246,6 +4244,82 @@ class ComputeBudgetProgram {
4246
4244
  }
4247
4245
  ComputeBudgetProgram.programId = new PublicKey('ComputeBudget111111111111111111111111111111');
4248
4246
 
4247
+ var objToString = Object.prototype.toString;
4248
+ var objKeys = Object.keys || function(obj) {
4249
+ var keys = [];
4250
+ for (var name in obj) {
4251
+ keys.push(name);
4252
+ }
4253
+ return keys;
4254
+ };
4255
+
4256
+ function stringify(val, isArrayProp) {
4257
+ var i, max, str, keys, key, propVal, toStr;
4258
+ if (val === true) {
4259
+ return "true";
4260
+ }
4261
+ if (val === false) {
4262
+ return "false";
4263
+ }
4264
+ switch (typeof val) {
4265
+ case "object":
4266
+ if (val === null) {
4267
+ return null;
4268
+ } else if (val.toJSON && typeof val.toJSON === "function") {
4269
+ return stringify(val.toJSON(), isArrayProp);
4270
+ } else {
4271
+ toStr = objToString.call(val);
4272
+ if (toStr === "[object Array]") {
4273
+ str = '[';
4274
+ max = val.length - 1;
4275
+ for(i = 0; i < max; i++) {
4276
+ str += stringify(val[i], true) + ',';
4277
+ }
4278
+ if (max > -1) {
4279
+ str += stringify(val[i], true);
4280
+ }
4281
+ return str + ']';
4282
+ } else if (toStr === "[object Object]") {
4283
+ // only object is left
4284
+ keys = objKeys(val).sort();
4285
+ max = keys.length;
4286
+ str = "";
4287
+ i = 0;
4288
+ while (i < max) {
4289
+ key = keys[i];
4290
+ propVal = stringify(val[key], false);
4291
+ if (propVal !== undefined) {
4292
+ if (str) {
4293
+ str += ',';
4294
+ }
4295
+ str += JSON.stringify(key) + ':' + propVal;
4296
+ }
4297
+ i++;
4298
+ }
4299
+ return '{' + str + '}';
4300
+ } else {
4301
+ return JSON.stringify(val);
4302
+ }
4303
+ }
4304
+ case "function":
4305
+ case "undefined":
4306
+ return isArrayProp ? null : undefined;
4307
+ case "string":
4308
+ return JSON.stringify(val);
4309
+ default:
4310
+ return isFinite(val) ? val : null;
4311
+ }
4312
+ }
4313
+
4314
+ var fastStableStringify = function(val) {
4315
+ var returnVal = stringify(val, false);
4316
+ if (returnVal !== undefined) {
4317
+ return ''+ returnVal;
4318
+ }
4319
+ };
4320
+
4321
+ var fastStableStringify$1 = fastStableStringify;
4322
+
4249
4323
  const DESTROY_TIMEOUT_MS = 5000;
4250
4324
  class AgentManager {
4251
4325
  static _newAgent(useHttps) {
@@ -4461,6 +4535,12 @@ const BufferFromRawAccountData = superstruct.coerce(superstruct.instance(buffer.
4461
4535
  */
4462
4536
 
4463
4537
  const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
4538
+ /**
4539
+ * HACK.
4540
+ * Copied from rpc-websockets/dist/lib/client.
4541
+ * Otherwise, `yarn build` fails with:
4542
+ * https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d
4543
+ */
4464
4544
 
4465
4545
  /**
4466
4546
  * @internal
@@ -5335,14 +5415,9 @@ const LogsNotificationResult = superstruct.type({
5335
5415
  * Filter for log subscriptions.
5336
5416
  */
5337
5417
 
5338
- function createSubscriptionWarningMessage(id, label) {
5339
- return 'Ignored unsubscribe request because an active subscription ' + `with id \`${id}\` for '${label}' events could not be found.`;
5340
- }
5341
5418
  /**
5342
5419
  * A connection to a fullnode JSON RPC endpoint
5343
5420
  */
5344
-
5345
-
5346
5421
  class Connection {
5347
5422
  /** @internal */
5348
5423
 
@@ -5366,21 +5441,13 @@ class Connection {
5366
5441
 
5367
5442
  /** @internal */
5368
5443
 
5369
- /** @internal */
5370
-
5371
- /** @internal */
5372
-
5373
- /** @internal */
5374
-
5375
- /** @internal */
5376
-
5377
- /** @internal */
5378
-
5379
- /** @internal */
5380
-
5381
- /** @internal */
5382
-
5383
- /** @internal */
5444
+ /** @internal
5445
+ * A number that we increment every time an active connection closes.
5446
+ * Used to determine whether the same socket connection that was open
5447
+ * when an async operation started is the same one that's active when
5448
+ * its continuation fires.
5449
+ *
5450
+ */
5384
5451
 
5385
5452
  /** @internal */
5386
5453
 
@@ -5396,7 +5463,19 @@ class Connection {
5396
5463
 
5397
5464
  /** @internal */
5398
5465
 
5399
- /** @internal */
5466
+ /**
5467
+ * Special case.
5468
+ * After a signature is processed, RPCs automatically dispose of the
5469
+ * subscription on the server side. We need to track which of these
5470
+ * subscriptions have been disposed in such a way, so that we know
5471
+ * whether the client is dealing with a not-yet-processed signature
5472
+ * (in which case we must tear down the server subscription) or an
5473
+ * already-processed signature (in which case the client can simply
5474
+ * clear out the subscription locally without telling the server).
5475
+ *
5476
+ * NOTE: There is a proposal to eliminate this special case, here:
5477
+ * https://github.com/solana-labs/solana/issues/18892
5478
+ */
5400
5479
 
5401
5480
  /** @internal */
5402
5481
 
@@ -5418,6 +5497,7 @@ class Connection {
5418
5497
  this._rpcWebSocketConnected = false;
5419
5498
  this._rpcWebSocketHeartbeat = null;
5420
5499
  this._rpcWebSocketIdleTimeout = null;
5500
+ this._rpcWebSocketGeneration = 0;
5421
5501
  this._disableBlockhashCaching = false;
5422
5502
  this._pollingBlockhash = false;
5423
5503
  this._blockhashInfo = {
@@ -5426,20 +5506,11 @@ class Connection {
5426
5506
  transactionSignatures: [],
5427
5507
  simulatedSignatures: []
5428
5508
  };
5429
- this._accountChangeSubscriptionCounter = 0;
5430
- this._accountChangeSubscriptions = {};
5431
- this._programAccountChangeSubscriptionCounter = 0;
5432
- this._programAccountChangeSubscriptions = {};
5433
- this._rootSubscriptionCounter = 0;
5434
- this._rootSubscriptions = {};
5435
- this._signatureSubscriptionCounter = 0;
5436
- this._signatureSubscriptions = {};
5437
- this._slotSubscriptionCounter = 0;
5438
- this._slotSubscriptions = {};
5439
- this._logsSubscriptionCounter = 0;
5440
- this._logsSubscriptions = {};
5441
- this._slotUpdateSubscriptionCounter = 0;
5442
- this._slotUpdateSubscriptions = {};
5509
+ this._nextClientSubscriptionId = 0;
5510
+ this._subscriptionDisposeFunctionsByClientSubscriptionId = {};
5511
+ this._subscriptionCallbacksByServerSubscriptionId = {};
5512
+ this._subscriptionsByHash = {};
5513
+ this._subscriptionsAutoDisposedByRpc = new Set();
5443
5514
  let url = new URL(endpoint);
5444
5515
  const useHttps = url.protocol === 'https:';
5445
5516
  let wsEndpoint;
@@ -7207,6 +7278,8 @@ class Connection {
7207
7278
 
7208
7279
 
7209
7280
  _wsOnClose(code) {
7281
+ this._rpcWebSocketGeneration++;
7282
+
7210
7283
  if (this._rpcWebSocketHeartbeat) {
7211
7284
  clearInterval(this._rpcWebSocketHeartbeat);
7212
7285
  this._rpcWebSocketHeartbeat = null;
@@ -7220,85 +7293,20 @@ class Connection {
7220
7293
  } // implicit close, prepare subscriptions for auto-reconnect
7221
7294
 
7222
7295
 
7223
- this._resetSubscriptions();
7224
- }
7225
- /**
7226
- * @internal
7227
- */
7228
-
7229
-
7230
- async _subscribe(sub, rpcMethod, rpcArgs) {
7231
- if (sub.subscriptionId == null) {
7232
- sub.subscriptionId = 'subscribing';
7233
-
7234
- try {
7235
- const id = await this._rpcWebSocket.call(rpcMethod, rpcArgs);
7236
-
7237
- if (typeof id === 'number' && sub.subscriptionId === 'subscribing') {
7238
- // eslint-disable-next-line require-atomic-updates
7239
- sub.subscriptionId = id;
7240
- }
7241
- } catch (err) {
7242
- if (sub.subscriptionId === 'subscribing') {
7243
- // eslint-disable-next-line require-atomic-updates
7244
- sub.subscriptionId = null;
7245
- }
7246
-
7247
- if (err instanceof Error) {
7248
- console.error(`${rpcMethod} error for argument`, rpcArgs, err.message);
7249
- }
7250
- }
7251
- }
7252
- }
7253
- /**
7254
- * @internal
7255
- */
7256
-
7257
-
7258
- async _unsubscribe(sub, rpcMethod) {
7259
- const subscriptionId = sub.subscriptionId;
7260
-
7261
- if (subscriptionId != null && typeof subscriptionId != 'string') {
7262
- const unsubscribeId = subscriptionId;
7263
-
7264
- try {
7265
- await this._rpcWebSocket.call(rpcMethod, [unsubscribeId]);
7266
- } catch (err) {
7267
- if (err instanceof Error) {
7268
- console.error(`${rpcMethod} error:`, err.message);
7269
- }
7270
- }
7271
- }
7272
- }
7273
- /**
7274
- * @internal
7275
- */
7276
-
7277
-
7278
- _resetSubscriptions() {
7279
- Object.values(this._accountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7280
- Object.values(this._logsSubscriptions).forEach(s => s.subscriptionId = null);
7281
- Object.values(this._programAccountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7282
- Object.values(this._rootSubscriptions).forEach(s => s.subscriptionId = null);
7283
- Object.values(this._signatureSubscriptions).forEach(s => s.subscriptionId = null);
7284
- Object.values(this._slotSubscriptions).forEach(s => s.subscriptionId = null);
7285
- Object.values(this._slotUpdateSubscriptions).forEach(s => s.subscriptionId = null);
7296
+ this._subscriptionCallbacksByServerSubscriptionId = {};
7297
+ Object.entries(this._subscriptionsByHash).forEach(([hash, subscription]) => {
7298
+ this._subscriptionsByHash[hash] = { ...subscription,
7299
+ state: 'pending'
7300
+ };
7301
+ });
7286
7302
  }
7287
7303
  /**
7288
7304
  * @internal
7289
7305
  */
7290
7306
 
7291
7307
 
7292
- _updateSubscriptions() {
7293
- const accountKeys = Object.keys(this._accountChangeSubscriptions).map(Number);
7294
- const programKeys = Object.keys(this._programAccountChangeSubscriptions).map(Number);
7295
- const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
7296
- const slotUpdateKeys = Object.keys(this._slotUpdateSubscriptions).map(Number);
7297
- const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
7298
- const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
7299
- const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
7300
-
7301
- if (accountKeys.length === 0 && programKeys.length === 0 && slotKeys.length === 0 && slotUpdateKeys.length === 0 && signatureKeys.length === 0 && rootKeys.length === 0 && logsKeys.length === 0) {
7308
+ async _updateSubscriptions() {
7309
+ if (Object.keys(this._subscriptionsByHash).length === 0) {
7302
7310
  if (this._rpcWebSocketConnected) {
7303
7311
  this._rpcWebSocketConnected = false;
7304
7312
  this._rpcWebSocketIdleTimeout = setTimeout(() => {
@@ -7330,60 +7338,167 @@ class Connection {
7330
7338
  return;
7331
7339
  }
7332
7340
 
7333
- for (let id of accountKeys) {
7334
- const sub = this._accountChangeSubscriptions[id];
7341
+ const activeWebSocketGeneration = this._rpcWebSocketGeneration;
7335
7342
 
7336
- this._subscribe(sub, 'accountSubscribe', this._buildArgs([sub.publicKey], sub.commitment, 'base64'));
7337
- }
7338
-
7339
- for (let id of programKeys) {
7340
- const sub = this._programAccountChangeSubscriptions[id];
7343
+ const isCurrentConnectionStillActive = () => {
7344
+ return activeWebSocketGeneration === this._rpcWebSocketGeneration;
7345
+ };
7341
7346
 
7342
- this._subscribe(sub, 'programSubscribe', this._buildArgs([sub.programId], sub.commitment, 'base64', {
7343
- filters: sub.filters
7344
- }));
7345
- }
7347
+ await Promise.all( // Don't be tempted to change this to `Object.entries`. We call
7348
+ // `_updateSubscriptions` recursively when processing the state,
7349
+ // so it's important that we look up the *current* version of
7350
+ // each subscription, every time we process a hash.
7351
+ Object.keys(this._subscriptionsByHash).map(async hash => {
7352
+ const subscription = this._subscriptionsByHash[hash];
7346
7353
 
7347
- for (let id of slotKeys) {
7348
- const sub = this._slotSubscriptions[id];
7354
+ if (subscription === undefined) {
7355
+ // This entry has since been deleted. Skip.
7356
+ return;
7357
+ }
7349
7358
 
7350
- this._subscribe(sub, 'slotSubscribe', []);
7351
- }
7359
+ switch (subscription.state) {
7360
+ case 'pending':
7361
+ case 'unsubscribed':
7362
+ if (subscription.callbacks.size === 0) {
7363
+ /**
7364
+ * You can end up here when:
7365
+ *
7366
+ * - a subscription has recently unsubscribed
7367
+ * without having new callbacks added to it
7368
+ * while the unsubscribe was in flight, or
7369
+ * - when a pending subscription has its
7370
+ * listeners removed before a request was
7371
+ * sent to the server.
7372
+ *
7373
+ * Being that nobody is interested in this
7374
+ * subscription any longer, delete it.
7375
+ */
7376
+ delete this._subscriptionsByHash[hash];
7377
+
7378
+ if (subscription.state === 'unsubscribed') {
7379
+ delete this._subscriptionCallbacksByServerSubscriptionId[subscription.serverSubscriptionId];
7380
+ }
7352
7381
 
7353
- for (let id of slotUpdateKeys) {
7354
- const sub = this._slotUpdateSubscriptions[id];
7382
+ await this._updateSubscriptions();
7383
+ return;
7384
+ }
7355
7385
 
7356
- this._subscribe(sub, 'slotsUpdatesSubscribe', []);
7357
- }
7386
+ await (async () => {
7387
+ const {
7388
+ args,
7389
+ method
7390
+ } = subscription;
7358
7391
 
7359
- for (let id of signatureKeys) {
7360
- const sub = this._signatureSubscriptions[id];
7361
- const args = [sub.signature];
7362
- if (sub.options) args.push(sub.options);
7392
+ try {
7393
+ this._subscriptionsByHash[hash] = { ...subscription,
7394
+ state: 'subscribing'
7395
+ };
7396
+ const serverSubscriptionId = await this._rpcWebSocket.call(method, args);
7397
+ this._subscriptionsByHash[hash] = { ...subscription,
7398
+ serverSubscriptionId,
7399
+ state: 'subscribed'
7400
+ };
7401
+ this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId] = subscription.callbacks;
7402
+ await this._updateSubscriptions();
7403
+ } catch (e) {
7404
+ if (e instanceof Error) {
7405
+ console.error(`${method} error for argument`, args, e.message);
7406
+ }
7407
+
7408
+ if (!isCurrentConnectionStillActive()) {
7409
+ return;
7410
+ } // TODO: Maybe add an 'errored' state or a retry limit?
7363
7411
 
7364
- this._subscribe(sub, 'signatureSubscribe', args);
7365
- }
7366
7412
 
7367
- for (let id of rootKeys) {
7368
- const sub = this._rootSubscriptions[id];
7413
+ this._subscriptionsByHash[hash] = { ...subscription,
7414
+ state: 'pending'
7415
+ };
7416
+ await this._updateSubscriptions();
7417
+ }
7418
+ })();
7419
+ break;
7369
7420
 
7370
- this._subscribe(sub, 'rootSubscribe', []);
7371
- }
7421
+ case 'subscribed':
7422
+ if (subscription.callbacks.size === 0) {
7423
+ // By the time we successfully set up a subscription
7424
+ // with the server, the client stopped caring about it.
7425
+ // Tear it down now.
7426
+ await (async () => {
7427
+ const {
7428
+ serverSubscriptionId,
7429
+ unsubscribeMethod
7430
+ } = subscription;
7431
+
7432
+ if (this._subscriptionsAutoDisposedByRpc.has(serverSubscriptionId)) {
7433
+ /**
7434
+ * Special case.
7435
+ * If we're dealing with a subscription that has been auto-
7436
+ * disposed by the RPC, then we can skip the RPC call to
7437
+ * tear down the subscription here.
7438
+ *
7439
+ * NOTE: There is a proposal to eliminate this special case, here:
7440
+ * https://github.com/solana-labs/solana/issues/18892
7441
+ */
7442
+ this._subscriptionsAutoDisposedByRpc.delete(serverSubscriptionId);
7443
+ } else {
7444
+ this._subscriptionsByHash[hash] = { ...subscription,
7445
+ state: 'unsubscribing'
7446
+ };
7447
+
7448
+ try {
7449
+ await this._rpcWebSocket.call(unsubscribeMethod, [serverSubscriptionId]);
7450
+ } catch (e) {
7451
+ if (e instanceof Error) {
7452
+ console.error(`${unsubscribeMethod} error:`, e.message);
7453
+ }
7454
+
7455
+ if (!isCurrentConnectionStillActive()) {
7456
+ return;
7457
+ } // TODO: Maybe add an 'errored' state or a retry limit?
7458
+
7459
+
7460
+ this._subscriptionsByHash[hash] = { ...subscription,
7461
+ state: 'subscribed'
7462
+ };
7463
+ await this._updateSubscriptions();
7464
+ return;
7465
+ }
7466
+ }
7372
7467
 
7373
- for (let id of logsKeys) {
7374
- const sub = this._logsSubscriptions[id];
7375
- let filter;
7468
+ this._subscriptionsByHash[hash] = { ...subscription,
7469
+ state: 'unsubscribed'
7470
+ };
7471
+ await this._updateSubscriptions();
7472
+ })();
7473
+ }
7376
7474
 
7377
- if (typeof sub.filter === 'object') {
7378
- filter = {
7379
- mentions: [sub.filter.toString()]
7380
- };
7381
- } else {
7382
- filter = sub.filter;
7475
+ break;
7383
7476
  }
7477
+ }));
7478
+ }
7479
+ /**
7480
+ * @internal
7481
+ */
7384
7482
 
7385
- this._subscribe(sub, 'logsSubscribe', this._buildArgs([filter], sub.commitment));
7483
+
7484
+ _handleServerNotification(serverSubscriptionId, callbackArgs) {
7485
+ const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
7486
+
7487
+ if (callbacks === undefined) {
7488
+ return;
7386
7489
  }
7490
+
7491
+ callbacks.forEach(cb => {
7492
+ try {
7493
+ cb( // I failed to find a way to convince TypeScript that `cb` is of type
7494
+ // `TCallback` which is certainly compatible with `Parameters<TCallback>`.
7495
+ // See https://github.com/microsoft/TypeScript/issues/47615
7496
+ // @ts-ignore
7497
+ ...callbackArgs);
7498
+ } catch (e) {
7499
+ console.error(e);
7500
+ }
7501
+ });
7387
7502
  }
7388
7503
  /**
7389
7504
  * @internal
@@ -7391,14 +7506,71 @@ class Connection {
7391
7506
 
7392
7507
 
7393
7508
  _wsOnAccountNotification(notification) {
7394
- const res = superstruct.create(notification, AccountNotificationResult);
7509
+ const {
7510
+ result,
7511
+ subscription
7512
+ } = superstruct.create(notification, AccountNotificationResult);
7395
7513
 
7396
- for (const sub of Object.values(this._accountChangeSubscriptions)) {
7397
- if (sub.subscriptionId === res.subscription) {
7398
- sub.callback(res.result.value, res.result.context);
7399
- return;
7400
- }
7514
+ this._handleServerNotification(subscription, [result.value, result.context]);
7515
+ }
7516
+ /**
7517
+ * @internal
7518
+ */
7519
+
7520
+
7521
+ _makeSubscription(subscriptionConfig,
7522
+ /**
7523
+ * When preparing `args` for a call to `_makeSubscription`, be sure
7524
+ * to carefully apply a default `commitment` property, if necessary.
7525
+ *
7526
+ * - If the user supplied a `commitment` use that.
7527
+ * - Otherwise, if the `Connection::commitment` is set, use that.
7528
+ * - Otherwise, set it to the RPC server default: `finalized`.
7529
+ *
7530
+ * This is extremely important to ensure that these two fundamentally
7531
+ * identical subscriptions produce the same identifying hash:
7532
+ *
7533
+ * - A subscription made without specifying a commitment.
7534
+ * - A subscription made where the commitment specified is the same
7535
+ * as the default applied to the subscription above.
7536
+ *
7537
+ * Example; these two subscriptions must produce the same hash:
7538
+ *
7539
+ * - An `accountSubscribe` subscription for `'PUBKEY'`
7540
+ * - An `accountSubscribe` subscription for `'PUBKEY'` with commitment
7541
+ * `'finalized'`.
7542
+ *
7543
+ * See the 'making a subscription with defaulted params omitted' test
7544
+ * in `connection-subscriptions.ts` for more.
7545
+ */
7546
+ args) {
7547
+ const clientSubscriptionId = this._nextClientSubscriptionId++;
7548
+ const hash = fastStableStringify$1([subscriptionConfig.method, args], true
7549
+ /* isArrayProp */
7550
+ );
7551
+ const existingSubscription = this._subscriptionsByHash[hash];
7552
+
7553
+ if (existingSubscription === undefined) {
7554
+ this._subscriptionsByHash[hash] = { ...subscriptionConfig,
7555
+ args,
7556
+ callbacks: new Set([subscriptionConfig.callback]),
7557
+ state: 'pending'
7558
+ };
7559
+ } else {
7560
+ existingSubscription.callbacks.add(subscriptionConfig.callback);
7401
7561
  }
7562
+
7563
+ this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId] = async () => {
7564
+ delete this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
7565
+ const subscription = this._subscriptionsByHash[hash];
7566
+ assert(subscription !== undefined, `Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`);
7567
+ subscription.callbacks.delete(subscriptionConfig.callback);
7568
+ await this._updateSubscriptions();
7569
+ };
7570
+
7571
+ this._updateSubscriptions();
7572
+
7573
+ return clientSubscriptionId;
7402
7574
  }
7403
7575
  /**
7404
7576
  * Register a callback to be invoked whenever the specified account changes
@@ -7411,35 +7583,24 @@ class Connection {
7411
7583
 
7412
7584
 
7413
7585
  onAccountChange(publicKey, callback, commitment) {
7414
- const id = ++this._accountChangeSubscriptionCounter;
7415
- this._accountChangeSubscriptions[id] = {
7416
- publicKey: publicKey.toBase58(),
7417
- callback,
7418
- commitment,
7419
- subscriptionId: null
7420
- };
7586
+ const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
7587
+ 'base64');
7421
7588
 
7422
- this._updateSubscriptions();
7423
-
7424
- return id;
7589
+ return this._makeSubscription({
7590
+ callback,
7591
+ method: 'accountSubscribe',
7592
+ unsubscribeMethod: 'accountUnsubscribe'
7593
+ }, args);
7425
7594
  }
7426
7595
  /**
7427
7596
  * Deregister an account notification callback
7428
7597
  *
7429
- * @param id subscription id to deregister
7598
+ * @param id client subscription id to deregister
7430
7599
  */
7431
7600
 
7432
7601
 
7433
- async removeAccountChangeListener(id) {
7434
- if (this._accountChangeSubscriptions[id]) {
7435
- const subInfo = this._accountChangeSubscriptions[id];
7436
- delete this._accountChangeSubscriptions[id];
7437
- await this._unsubscribe(subInfo, 'accountUnsubscribe');
7438
-
7439
- this._updateSubscriptions();
7440
- } else {
7441
- console.warn(createSubscriptionWarningMessage(id, 'account change'));
7442
- }
7602
+ async removeAccountChangeListener(clientSubscriptionId) {
7603
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change');
7443
7604
  }
7444
7605
  /**
7445
7606
  * @internal
@@ -7447,21 +7608,15 @@ class Connection {
7447
7608
 
7448
7609
 
7449
7610
  _wsOnProgramAccountNotification(notification) {
7450
- const res = superstruct.create(notification, ProgramAccountNotificationResult);
7611
+ const {
7612
+ result,
7613
+ subscription
7614
+ } = superstruct.create(notification, ProgramAccountNotificationResult);
7451
7615
 
7452
- for (const sub of Object.values(this._programAccountChangeSubscriptions)) {
7453
- if (sub.subscriptionId === res.subscription) {
7454
- const {
7455
- value,
7456
- context
7457
- } = res.result;
7458
- sub.callback({
7459
- accountId: value.pubkey,
7460
- accountInfo: value.account
7461
- }, context);
7462
- return;
7463
- }
7464
- }
7616
+ this._handleServerNotification(subscription, [{
7617
+ accountId: result.value.pubkey,
7618
+ accountInfo: result.value.account
7619
+ }, result.context]);
7465
7620
  }
7466
7621
  /**
7467
7622
  * Register a callback to be invoked whenever accounts owned by the
@@ -7476,36 +7631,30 @@ class Connection {
7476
7631
 
7477
7632
 
7478
7633
  onProgramAccountChange(programId, callback, commitment, filters) {
7479
- const id = ++this._programAccountChangeSubscriptionCounter;
7480
- this._programAccountChangeSubscriptions[id] = {
7481
- programId: programId.toBase58(),
7634
+ const args = this._buildArgs([programId.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
7635
+ 'base64'
7636
+ /* encoding */
7637
+ , filters ? {
7638
+ filters: filters
7639
+ } : undefined
7640
+ /* extra */
7641
+ );
7642
+
7643
+ return this._makeSubscription({
7482
7644
  callback,
7483
- commitment,
7484
- subscriptionId: null,
7485
- filters
7486
- };
7487
-
7488
- this._updateSubscriptions();
7489
-
7490
- return id;
7645
+ method: 'programSubscribe',
7646
+ unsubscribeMethod: 'programUnsubscribe'
7647
+ }, args);
7491
7648
  }
7492
7649
  /**
7493
7650
  * Deregister an account notification callback
7494
7651
  *
7495
- * @param id subscription id to deregister
7652
+ * @param id client subscription id to deregister
7496
7653
  */
7497
7654
 
7498
7655
 
7499
- async removeProgramAccountChangeListener(id) {
7500
- if (this._programAccountChangeSubscriptions[id]) {
7501
- const subInfo = this._programAccountChangeSubscriptions[id];
7502
- delete this._programAccountChangeSubscriptions[id];
7503
- await this._unsubscribe(subInfo, 'programUnsubscribe');
7504
-
7505
- this._updateSubscriptions();
7506
- } else {
7507
- console.warn(createSubscriptionWarningMessage(id, 'program account change'));
7508
- }
7656
+ async removeProgramAccountChangeListener(clientSubscriptionId) {
7657
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change');
7509
7658
  }
7510
7659
  /**
7511
7660
  * Registers a callback to be invoked whenever logs are emitted.
@@ -7513,35 +7662,26 @@ class Connection {
7513
7662
 
7514
7663
 
7515
7664
  onLogs(filter, callback, commitment) {
7516
- const id = ++this._logsSubscriptionCounter;
7517
- this._logsSubscriptions[id] = {
7518
- filter,
7519
- callback,
7520
- commitment,
7521
- subscriptionId: null
7522
- };
7665
+ const args = this._buildArgs([typeof filter === 'object' ? {
7666
+ mentions: [filter.toString()]
7667
+ } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default.
7668
+ );
7523
7669
 
7524
- this._updateSubscriptions();
7525
-
7526
- return id;
7670
+ return this._makeSubscription({
7671
+ callback,
7672
+ method: 'logsSubscribe',
7673
+ unsubscribeMethod: 'logsUnsubscribe'
7674
+ }, args);
7527
7675
  }
7528
7676
  /**
7529
7677
  * Deregister a logs callback.
7530
7678
  *
7531
- * @param id subscription id to deregister.
7679
+ * @param id client subscription id to deregister.
7532
7680
  */
7533
7681
 
7534
7682
 
7535
- async removeOnLogsListener(id) {
7536
- if (this._logsSubscriptions[id]) {
7537
- const subInfo = this._logsSubscriptions[id];
7538
- delete this._logsSubscriptions[id];
7539
- await this._unsubscribe(subInfo, 'logsUnsubscribe');
7540
-
7541
- this._updateSubscriptions();
7542
- } else {
7543
- console.warn(createSubscriptionWarningMessage(id, 'logs'));
7544
- }
7683
+ async removeOnLogsListener(clientSubscriptionId) {
7684
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs');
7545
7685
  }
7546
7686
  /**
7547
7687
  * @internal
@@ -7549,17 +7689,12 @@ class Connection {
7549
7689
 
7550
7690
 
7551
7691
  _wsOnLogsNotification(notification) {
7552
- const res = superstruct.create(notification, LogsNotificationResult);
7553
- const keys = Object.keys(this._logsSubscriptions).map(Number);
7554
-
7555
- for (let id of keys) {
7556
- const sub = this._logsSubscriptions[id];
7692
+ const {
7693
+ result,
7694
+ subscription
7695
+ } = superstruct.create(notification, LogsNotificationResult);
7557
7696
 
7558
- if (sub.subscriptionId === res.subscription) {
7559
- sub.callback(res.result.value, res.result.context);
7560
- return;
7561
- }
7562
- }
7697
+ this._handleServerNotification(subscription, [result.value, result.context]);
7563
7698
  }
7564
7699
  /**
7565
7700
  * @internal
@@ -7567,14 +7702,12 @@ class Connection {
7567
7702
 
7568
7703
 
7569
7704
  _wsOnSlotNotification(notification) {
7570
- const res = superstruct.create(notification, SlotNotificationResult);
7705
+ const {
7706
+ result,
7707
+ subscription
7708
+ } = superstruct.create(notification, SlotNotificationResult);
7571
7709
 
7572
- for (const sub of Object.values(this._slotSubscriptions)) {
7573
- if (sub.subscriptionId === res.subscription) {
7574
- sub.callback(res.result);
7575
- return;
7576
- }
7577
- }
7710
+ this._handleServerNotification(subscription, [result]);
7578
7711
  }
7579
7712
  /**
7580
7713
  * Register a callback to be invoked upon slot changes
@@ -7585,33 +7718,23 @@ class Connection {
7585
7718
 
7586
7719
 
7587
7720
  onSlotChange(callback) {
7588
- const id = ++this._slotSubscriptionCounter;
7589
- this._slotSubscriptions[id] = {
7721
+ return this._makeSubscription({
7590
7722
  callback,
7591
- subscriptionId: null
7592
- };
7593
-
7594
- this._updateSubscriptions();
7595
-
7596
- return id;
7723
+ method: 'slotSubscribe',
7724
+ unsubscribeMethod: 'slotUnsubscribe'
7725
+ }, []
7726
+ /* args */
7727
+ );
7597
7728
  }
7598
7729
  /**
7599
7730
  * Deregister a slot notification callback
7600
7731
  *
7601
- * @param id subscription id to deregister
7732
+ * @param id client subscription id to deregister
7602
7733
  */
7603
7734
 
7604
7735
 
7605
- async removeSlotChangeListener(id) {
7606
- if (this._slotSubscriptions[id]) {
7607
- const subInfo = this._slotSubscriptions[id];
7608
- delete this._slotSubscriptions[id];
7609
- await this._unsubscribe(subInfo, 'slotUnsubscribe');
7610
-
7611
- this._updateSubscriptions();
7612
- } else {
7613
- console.warn(createSubscriptionWarningMessage(id, 'slot change'));
7614
- }
7736
+ async removeSlotChangeListener(clientSubscriptionId) {
7737
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change');
7615
7738
  }
7616
7739
  /**
7617
7740
  * @internal
@@ -7619,14 +7742,12 @@ class Connection {
7619
7742
 
7620
7743
 
7621
7744
  _wsOnSlotUpdatesNotification(notification) {
7622
- const res = superstruct.create(notification, SlotUpdateNotificationResult);
7745
+ const {
7746
+ result,
7747
+ subscription
7748
+ } = superstruct.create(notification, SlotUpdateNotificationResult);
7623
7749
 
7624
- for (const sub of Object.values(this._slotUpdateSubscriptions)) {
7625
- if (sub.subscriptionId === res.subscription) {
7626
- sub.callback(res.result);
7627
- return;
7628
- }
7629
- }
7750
+ this._handleServerNotification(subscription, [result]);
7630
7751
  }
7631
7752
  /**
7632
7753
  * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
@@ -7638,32 +7759,36 @@ class Connection {
7638
7759
 
7639
7760
 
7640
7761
  onSlotUpdate(callback) {
7641
- const id = ++this._slotUpdateSubscriptionCounter;
7642
- this._slotUpdateSubscriptions[id] = {
7762
+ return this._makeSubscription({
7643
7763
  callback,
7644
- subscriptionId: null
7645
- };
7646
-
7647
- this._updateSubscriptions();
7648
-
7649
- return id;
7764
+ method: 'slotsUpdatesSubscribe',
7765
+ unsubscribeMethod: 'slotsUpdatesUnsubscribe'
7766
+ }, []
7767
+ /* args */
7768
+ );
7650
7769
  }
7651
7770
  /**
7652
7771
  * Deregister a slot update notification callback
7653
7772
  *
7654
- * @param id subscription id to deregister
7773
+ * @param id client subscription id to deregister
7655
7774
  */
7656
7775
 
7657
7776
 
7658
- async removeSlotUpdateListener(id) {
7659
- if (this._slotUpdateSubscriptions[id]) {
7660
- const subInfo = this._slotUpdateSubscriptions[id];
7661
- delete this._slotUpdateSubscriptions[id];
7662
- await this._unsubscribe(subInfo, 'slotsUpdatesUnsubscribe');
7777
+ async removeSlotUpdateListener(clientSubscriptionId) {
7778
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update');
7779
+ }
7780
+ /**
7781
+ * @internal
7782
+ */
7663
7783
 
7664
- this._updateSubscriptions();
7784
+
7785
+ async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) {
7786
+ const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
7787
+
7788
+ if (dispose) {
7789
+ await dispose();
7665
7790
  } else {
7666
- console.warn(createSubscriptionWarningMessage(id, 'slot update'));
7791
+ console.warn('Ignored unsubscribe request because an active subscription with id ' + `\`${clientSubscriptionId}\` for '${subscriptionName}' events ` + 'could not be found.');
7667
7792
  }
7668
7793
  }
7669
7794
 
@@ -7710,30 +7835,34 @@ class Connection {
7710
7835
 
7711
7836
 
7712
7837
  _wsOnSignatureNotification(notification) {
7713
- const res = superstruct.create(notification, SignatureNotificationResult);
7714
-
7715
- for (const [id, sub] of Object.entries(this._signatureSubscriptions)) {
7716
- if (sub.subscriptionId === res.subscription) {
7717
- if (res.result.value === 'receivedSignature') {
7718
- sub.callback({
7719
- type: 'received'
7720
- }, res.result.context);
7721
- } else {
7722
- // Signatures subscriptions are auto-removed by the RPC service so
7723
- // no need to explicitly send an unsubscribe message
7724
- delete this._signatureSubscriptions[Number(id)];
7725
-
7726
- this._updateSubscriptions();
7727
-
7728
- sub.callback({
7729
- type: 'status',
7730
- result: res.result.value
7731
- }, res.result.context);
7732
- }
7733
-
7734
- return;
7735
- }
7736
- }
7838
+ const {
7839
+ result,
7840
+ subscription
7841
+ } = superstruct.create(notification, SignatureNotificationResult);
7842
+
7843
+ if (result.value !== 'receivedSignature') {
7844
+ /**
7845
+ * Special case.
7846
+ * After a signature is processed, RPCs automatically dispose of the
7847
+ * subscription on the server side. We need to track which of these
7848
+ * subscriptions have been disposed in such a way, so that we know
7849
+ * whether the client is dealing with a not-yet-processed signature
7850
+ * (in which case we must tear down the server subscription) or an
7851
+ * already-processed signature (in which case the client can simply
7852
+ * clear out the subscription locally without telling the server).
7853
+ *
7854
+ * NOTE: There is a proposal to eliminate this special case, here:
7855
+ * https://github.com/solana-labs/solana/issues/18892
7856
+ */
7857
+ this._subscriptionsAutoDisposedByRpc.add(subscription);
7858
+ }
7859
+
7860
+ this._handleServerNotification(subscription, result.value === 'receivedSignature' ? [{
7861
+ type: 'received'
7862
+ }, result.context] : [{
7863
+ type: 'status',
7864
+ result: result.value
7865
+ }, result.context]);
7737
7866
  }
7738
7867
  /**
7739
7868
  * Register a callback to be invoked upon signature updates
@@ -7746,23 +7875,26 @@ class Connection {
7746
7875
 
7747
7876
 
7748
7877
  onSignature(signature, callback, commitment) {
7749
- const id = ++this._signatureSubscriptionCounter;
7750
- this._signatureSubscriptions[id] = {
7751
- signature,
7878
+ const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default.
7879
+ );
7880
+
7881
+ const clientSubscriptionId = this._makeSubscription({
7752
7882
  callback: (notification, context) => {
7753
7883
  if (notification.type === 'status') {
7754
- callback(notification.result, context);
7884
+ callback(notification.result, context); // Signatures subscriptions are auto-removed by the RPC service
7885
+ // so no need to explicitly send an unsubscribe message.
7886
+
7887
+ try {
7888
+ this.removeSignatureListener(clientSubscriptionId); // eslint-disable-next-line no-empty
7889
+ } catch {// Already removed.
7890
+ }
7755
7891
  }
7756
7892
  },
7757
- options: {
7758
- commitment
7759
- },
7760
- subscriptionId: null
7761
- };
7762
-
7763
- this._updateSubscriptions();
7893
+ method: 'signatureSubscribe',
7894
+ unsubscribeMethod: 'signatureUnsubscribe'
7895
+ }, args);
7764
7896
 
7765
- return id;
7897
+ return clientSubscriptionId;
7766
7898
  }
7767
7899
  /**
7768
7900
  * Register a callback to be invoked when a transaction is
@@ -7777,35 +7909,43 @@ class Connection {
7777
7909
 
7778
7910
 
7779
7911
  onSignatureWithOptions(signature, callback, options) {
7780
- const id = ++this._signatureSubscriptionCounter;
7781
- this._signatureSubscriptions[id] = {
7782
- signature,
7783
- callback,
7784
- options,
7785
- subscriptionId: null
7912
+ const {
7913
+ commitment,
7914
+ ...extra
7915
+ } = { ...options,
7916
+ commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default.
7917
+
7786
7918
  };
7787
7919
 
7788
- this._updateSubscriptions();
7920
+ const args = this._buildArgs([signature], commitment, undefined
7921
+ /* encoding */
7922
+ , extra);
7923
+
7924
+ const clientSubscriptionId = this._makeSubscription({
7925
+ callback: (notification, context) => {
7926
+ callback(notification, context); // Signatures subscriptions are auto-removed by the RPC service
7927
+ // so no need to explicitly send an unsubscribe message.
7928
+
7929
+ try {
7930
+ this.removeSignatureListener(clientSubscriptionId); // eslint-disable-next-line no-empty
7931
+ } catch {// Already removed.
7932
+ }
7933
+ },
7934
+ method: 'signatureSubscribe',
7935
+ unsubscribeMethod: 'signatureUnsubscribe'
7936
+ }, args);
7789
7937
 
7790
- return id;
7938
+ return clientSubscriptionId;
7791
7939
  }
7792
7940
  /**
7793
7941
  * Deregister a signature notification callback
7794
7942
  *
7795
- * @param id subscription id to deregister
7943
+ * @param id client subscription id to deregister
7796
7944
  */
7797
7945
 
7798
7946
 
7799
- async removeSignatureListener(id) {
7800
- if (this._signatureSubscriptions[id]) {
7801
- const subInfo = this._signatureSubscriptions[id];
7802
- delete this._signatureSubscriptions[id];
7803
- await this._unsubscribe(subInfo, 'signatureUnsubscribe');
7804
-
7805
- this._updateSubscriptions();
7806
- } else {
7807
- console.warn(createSubscriptionWarningMessage(id, 'signature result'));
7808
- }
7947
+ async removeSignatureListener(clientSubscriptionId) {
7948
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result');
7809
7949
  }
7810
7950
  /**
7811
7951
  * @internal
@@ -7813,14 +7953,12 @@ class Connection {
7813
7953
 
7814
7954
 
7815
7955
  _wsOnRootNotification(notification) {
7816
- const res = superstruct.create(notification, RootNotificationResult);
7956
+ const {
7957
+ result,
7958
+ subscription
7959
+ } = superstruct.create(notification, RootNotificationResult);
7817
7960
 
7818
- for (const sub of Object.values(this._rootSubscriptions)) {
7819
- if (sub.subscriptionId === res.subscription) {
7820
- sub.callback(res.result);
7821
- return;
7822
- }
7823
- }
7961
+ this._handleServerNotification(subscription, [result]);
7824
7962
  }
7825
7963
  /**
7826
7964
  * Register a callback to be invoked upon root changes
@@ -7831,33 +7969,23 @@ class Connection {
7831
7969
 
7832
7970
 
7833
7971
  onRootChange(callback) {
7834
- const id = ++this._rootSubscriptionCounter;
7835
- this._rootSubscriptions[id] = {
7972
+ return this._makeSubscription({
7836
7973
  callback,
7837
- subscriptionId: null
7838
- };
7839
-
7840
- this._updateSubscriptions();
7841
-
7842
- return id;
7974
+ method: 'rootSubscribe',
7975
+ unsubscribeMethod: 'rootUnsubscribe'
7976
+ }, []
7977
+ /* args */
7978
+ );
7843
7979
  }
7844
7980
  /**
7845
7981
  * Deregister a root notification callback
7846
7982
  *
7847
- * @param id subscription id to deregister
7983
+ * @param id client subscription id to deregister
7848
7984
  */
7849
7985
 
7850
7986
 
7851
- async removeRootChangeListener(id) {
7852
- if (this._rootSubscriptions[id]) {
7853
- const subInfo = this._rootSubscriptions[id];
7854
- delete this._rootSubscriptions[id];
7855
- await this._unsubscribe(subInfo, 'rootUnsubscribe');
7856
-
7857
- this._updateSubscriptions();
7858
- } else {
7859
- console.warn(createSubscriptionWarningMessage(id, 'root change'));
7860
- }
7987
+ async removeRootChangeListener(clientSubscriptionId) {
7988
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change');
7861
7989
  }
7862
7990
 
7863
7991
  }
@@ -9561,6 +9689,7 @@ exports.NONCE_ACCOUNT_LENGTH = NONCE_ACCOUNT_LENGTH;
9561
9689
  exports.NonceAccount = NonceAccount;
9562
9690
  exports.PACKET_DATA_SIZE = PACKET_DATA_SIZE;
9563
9691
  exports.PublicKey = PublicKey;
9692
+ exports.SIGNATURE_LENGTH_IN_BYTES = SIGNATURE_LENGTH_IN_BYTES;
9564
9693
  exports.SOLANA_SCHEMA = SOLANA_SCHEMA;
9565
9694
  exports.STAKE_CONFIG_ID = STAKE_CONFIG_ID;
9566
9695
  exports.STAKE_INSTRUCTION_LAYOUTS = STAKE_INSTRUCTION_LAYOUTS;