@solana/web3.js 1.40.1 → 1.41.2

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
- */
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
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
@@ -4116,6 +4114,212 @@ class BpfLoader {
4116
4114
 
4117
4115
  }
4118
4116
 
4117
+ /**
4118
+ * Compute Budget Instruction class
4119
+ */
4120
+
4121
+ class ComputeBudgetInstruction {
4122
+ /**
4123
+ * @internal
4124
+ */
4125
+ constructor() {}
4126
+ /**
4127
+ * Decode a compute budget instruction and retrieve the instruction type.
4128
+ */
4129
+
4130
+
4131
+ static decodeInstructionType(instruction) {
4132
+ this.checkProgramId(instruction.programId);
4133
+ const instructionTypeLayout = BufferLayout__namespace.u8('instruction');
4134
+ const typeIndex = instructionTypeLayout.decode(instruction.data);
4135
+ let type;
4136
+
4137
+ for (const [ixType, layout] of Object.entries(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS)) {
4138
+ if (layout.index == typeIndex) {
4139
+ type = ixType;
4140
+ break;
4141
+ }
4142
+ }
4143
+
4144
+ if (!type) {
4145
+ throw new Error('Instruction type incorrect; not a ComputeBudgetInstruction');
4146
+ }
4147
+
4148
+ return type;
4149
+ }
4150
+ /**
4151
+ * Decode request units compute budget instruction and retrieve the instruction params.
4152
+ */
4153
+
4154
+
4155
+ static decodeRequestUnits(instruction) {
4156
+ this.checkProgramId(instruction.programId);
4157
+ const {
4158
+ units,
4159
+ additionalFee
4160
+ } = decodeData(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits, instruction.data);
4161
+ return {
4162
+ units,
4163
+ additionalFee
4164
+ };
4165
+ }
4166
+ /**
4167
+ * Decode request heap frame compute budget instruction and retrieve the instruction params.
4168
+ */
4169
+
4170
+
4171
+ static decodeRequestHeapFrame(instruction) {
4172
+ this.checkProgramId(instruction.programId);
4173
+ const {
4174
+ bytes
4175
+ } = decodeData(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame, instruction.data);
4176
+ return {
4177
+ bytes
4178
+ };
4179
+ }
4180
+ /**
4181
+ * @internal
4182
+ */
4183
+
4184
+
4185
+ static checkProgramId(programId) {
4186
+ if (!programId.equals(ComputeBudgetProgram.programId)) {
4187
+ throw new Error('invalid instruction; programId is not ComputeBudgetProgram');
4188
+ }
4189
+ }
4190
+
4191
+ }
4192
+ /**
4193
+ * An enumeration of valid ComputeBudgetInstructionType's
4194
+ */
4195
+
4196
+ /**
4197
+ * An enumeration of valid ComputeBudget InstructionType's
4198
+ * @internal
4199
+ */
4200
+ const COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = Object.freeze({
4201
+ RequestUnits: {
4202
+ index: 0,
4203
+ layout: BufferLayout__namespace.struct([BufferLayout__namespace.u8('instruction'), BufferLayout__namespace.u32('units'), BufferLayout__namespace.u32('additionalFee')])
4204
+ },
4205
+ RequestHeapFrame: {
4206
+ index: 1,
4207
+ layout: BufferLayout__namespace.struct([BufferLayout__namespace.u8('instruction'), BufferLayout__namespace.u32('bytes')])
4208
+ }
4209
+ });
4210
+ /**
4211
+ * Factory class for transaction instructions to interact with the Compute Budget program
4212
+ */
4213
+
4214
+ class ComputeBudgetProgram {
4215
+ /**
4216
+ * @internal
4217
+ */
4218
+ constructor() {}
4219
+ /**
4220
+ * Public key that identifies the Compute Budget program
4221
+ */
4222
+
4223
+
4224
+ static requestUnits(params) {
4225
+ const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits;
4226
+ const data = encodeData(type, params);
4227
+ return new TransactionInstruction({
4228
+ keys: [],
4229
+ programId: this.programId,
4230
+ data
4231
+ });
4232
+ }
4233
+
4234
+ static requestHeapFrame(params) {
4235
+ const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame;
4236
+ const data = encodeData(type, params);
4237
+ return new TransactionInstruction({
4238
+ keys: [],
4239
+ programId: this.programId,
4240
+ data
4241
+ });
4242
+ }
4243
+
4244
+ }
4245
+ ComputeBudgetProgram.programId = new PublicKey('ComputeBudget111111111111111111111111111111');
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
+
4119
4323
  const DESTROY_TIMEOUT_MS = 5000;
4120
4324
  class AgentManager {
4121
4325
  static _newAgent(useHttps) {
@@ -4331,6 +4535,12 @@ const BufferFromRawAccountData = superstruct.coerce(superstruct.instance(buffer.
4331
4535
  */
4332
4536
 
4333
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
+ */
4334
4544
 
4335
4545
  /**
4336
4546
  * @internal
@@ -5205,14 +5415,9 @@ const LogsNotificationResult = superstruct.type({
5205
5415
  * Filter for log subscriptions.
5206
5416
  */
5207
5417
 
5208
- function createSubscriptionWarningMessage(id, label) {
5209
- return 'Ignored unsubscribe request because an active subscription ' + `with id \`${id}\` for '${label}' events could not be found.`;
5210
- }
5211
5418
  /**
5212
5419
  * A connection to a fullnode JSON RPC endpoint
5213
5420
  */
5214
-
5215
-
5216
5421
  class Connection {
5217
5422
  /** @internal */
5218
5423
 
@@ -5236,21 +5441,13 @@ class Connection {
5236
5441
 
5237
5442
  /** @internal */
5238
5443
 
5239
- /** @internal */
5240
-
5241
- /** @internal */
5242
-
5243
- /** @internal */
5244
-
5245
- /** @internal */
5246
-
5247
- /** @internal */
5248
-
5249
- /** @internal */
5250
-
5251
- /** @internal */
5252
-
5253
- /** @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
+ */
5254
5451
 
5255
5452
  /** @internal */
5256
5453
 
@@ -5266,7 +5463,19 @@ class Connection {
5266
5463
 
5267
5464
  /** @internal */
5268
5465
 
5269
- /** @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
+ */
5270
5479
 
5271
5480
  /** @internal */
5272
5481
 
@@ -5288,6 +5497,7 @@ class Connection {
5288
5497
  this._rpcWebSocketConnected = false;
5289
5498
  this._rpcWebSocketHeartbeat = null;
5290
5499
  this._rpcWebSocketIdleTimeout = null;
5500
+ this._rpcWebSocketGeneration = 0;
5291
5501
  this._disableBlockhashCaching = false;
5292
5502
  this._pollingBlockhash = false;
5293
5503
  this._blockhashInfo = {
@@ -5296,20 +5506,11 @@ class Connection {
5296
5506
  transactionSignatures: [],
5297
5507
  simulatedSignatures: []
5298
5508
  };
5299
- this._accountChangeSubscriptionCounter = 0;
5300
- this._accountChangeSubscriptions = {};
5301
- this._programAccountChangeSubscriptionCounter = 0;
5302
- this._programAccountChangeSubscriptions = {};
5303
- this._rootSubscriptionCounter = 0;
5304
- this._rootSubscriptions = {};
5305
- this._signatureSubscriptionCounter = 0;
5306
- this._signatureSubscriptions = {};
5307
- this._slotSubscriptionCounter = 0;
5308
- this._slotSubscriptions = {};
5309
- this._logsSubscriptionCounter = 0;
5310
- this._logsSubscriptions = {};
5311
- this._slotUpdateSubscriptionCounter = 0;
5312
- this._slotUpdateSubscriptions = {};
5509
+ this._nextClientSubscriptionId = 0;
5510
+ this._subscriptionDisposeFunctionsByClientSubscriptionId = {};
5511
+ this._subscriptionCallbacksByServerSubscriptionId = {};
5512
+ this._subscriptionsByHash = {};
5513
+ this._subscriptionsAutoDisposedByRpc = new Set();
5313
5514
  let url = new URL(endpoint);
5314
5515
  const useHttps = url.protocol === 'https:';
5315
5516
  let wsEndpoint;
@@ -7077,6 +7278,8 @@ class Connection {
7077
7278
 
7078
7279
 
7079
7280
  _wsOnClose(code) {
7281
+ this._rpcWebSocketGeneration++;
7282
+
7080
7283
  if (this._rpcWebSocketHeartbeat) {
7081
7284
  clearInterval(this._rpcWebSocketHeartbeat);
7082
7285
  this._rpcWebSocketHeartbeat = null;
@@ -7090,85 +7293,20 @@ class Connection {
7090
7293
  } // implicit close, prepare subscriptions for auto-reconnect
7091
7294
 
7092
7295
 
7093
- this._resetSubscriptions();
7094
- }
7095
- /**
7096
- * @internal
7097
- */
7098
-
7099
-
7100
- async _subscribe(sub, rpcMethod, rpcArgs) {
7101
- if (sub.subscriptionId == null) {
7102
- sub.subscriptionId = 'subscribing';
7103
-
7104
- try {
7105
- const id = await this._rpcWebSocket.call(rpcMethod, rpcArgs);
7106
-
7107
- if (typeof id === 'number' && sub.subscriptionId === 'subscribing') {
7108
- // eslint-disable-next-line require-atomic-updates
7109
- sub.subscriptionId = id;
7110
- }
7111
- } catch (err) {
7112
- if (sub.subscriptionId === 'subscribing') {
7113
- // eslint-disable-next-line require-atomic-updates
7114
- sub.subscriptionId = null;
7115
- }
7116
-
7117
- if (err instanceof Error) {
7118
- console.error(`${rpcMethod} error for argument`, rpcArgs, err.message);
7119
- }
7120
- }
7121
- }
7122
- }
7123
- /**
7124
- * @internal
7125
- */
7126
-
7127
-
7128
- async _unsubscribe(sub, rpcMethod) {
7129
- const subscriptionId = sub.subscriptionId;
7130
-
7131
- if (subscriptionId != null && typeof subscriptionId != 'string') {
7132
- const unsubscribeId = subscriptionId;
7133
-
7134
- try {
7135
- await this._rpcWebSocket.call(rpcMethod, [unsubscribeId]);
7136
- } catch (err) {
7137
- if (err instanceof Error) {
7138
- console.error(`${rpcMethod} error:`, err.message);
7139
- }
7140
- }
7141
- }
7142
- }
7143
- /**
7144
- * @internal
7145
- */
7146
-
7147
-
7148
- _resetSubscriptions() {
7149
- Object.values(this._accountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7150
- Object.values(this._logsSubscriptions).forEach(s => s.subscriptionId = null);
7151
- Object.values(this._programAccountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7152
- Object.values(this._rootSubscriptions).forEach(s => s.subscriptionId = null);
7153
- Object.values(this._signatureSubscriptions).forEach(s => s.subscriptionId = null);
7154
- Object.values(this._slotSubscriptions).forEach(s => s.subscriptionId = null);
7155
- 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
+ });
7156
7302
  }
7157
7303
  /**
7158
7304
  * @internal
7159
7305
  */
7160
7306
 
7161
7307
 
7162
- _updateSubscriptions() {
7163
- const accountKeys = Object.keys(this._accountChangeSubscriptions).map(Number);
7164
- const programKeys = Object.keys(this._programAccountChangeSubscriptions).map(Number);
7165
- const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
7166
- const slotUpdateKeys = Object.keys(this._slotUpdateSubscriptions).map(Number);
7167
- const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
7168
- const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
7169
- const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
7170
-
7171
- 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) {
7172
7310
  if (this._rpcWebSocketConnected) {
7173
7311
  this._rpcWebSocketConnected = false;
7174
7312
  this._rpcWebSocketIdleTimeout = setTimeout(() => {
@@ -7200,60 +7338,167 @@ class Connection {
7200
7338
  return;
7201
7339
  }
7202
7340
 
7203
- for (let id of accountKeys) {
7204
- const sub = this._accountChangeSubscriptions[id];
7341
+ const activeWebSocketGeneration = this._rpcWebSocketGeneration;
7205
7342
 
7206
- this._subscribe(sub, 'accountSubscribe', this._buildArgs([sub.publicKey], sub.commitment, 'base64'));
7207
- }
7208
-
7209
- for (let id of programKeys) {
7210
- const sub = this._programAccountChangeSubscriptions[id];
7343
+ const isCurrentConnectionStillActive = () => {
7344
+ return activeWebSocketGeneration === this._rpcWebSocketGeneration;
7345
+ };
7211
7346
 
7212
- this._subscribe(sub, 'programSubscribe', this._buildArgs([sub.programId], sub.commitment, 'base64', {
7213
- filters: sub.filters
7214
- }));
7215
- }
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];
7216
7353
 
7217
- for (let id of slotKeys) {
7218
- const sub = this._slotSubscriptions[id];
7354
+ if (subscription === undefined) {
7355
+ // This entry has since been deleted. Skip.
7356
+ return;
7357
+ }
7219
7358
 
7220
- this._subscribe(sub, 'slotSubscribe', []);
7221
- }
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
+ }
7222
7381
 
7223
- for (let id of slotUpdateKeys) {
7224
- const sub = this._slotUpdateSubscriptions[id];
7382
+ await this._updateSubscriptions();
7383
+ return;
7384
+ }
7225
7385
 
7226
- this._subscribe(sub, 'slotsUpdatesSubscribe', []);
7227
- }
7386
+ await (async () => {
7387
+ const {
7388
+ args,
7389
+ method
7390
+ } = subscription;
7228
7391
 
7229
- for (let id of signatureKeys) {
7230
- const sub = this._signatureSubscriptions[id];
7231
- const args = [sub.signature];
7232
- 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?
7233
7411
 
7234
- this._subscribe(sub, 'signatureSubscribe', args);
7235
- }
7236
7412
 
7237
- for (let id of rootKeys) {
7238
- const sub = this._rootSubscriptions[id];
7413
+ this._subscriptionsByHash[hash] = { ...subscription,
7414
+ state: 'pending'
7415
+ };
7416
+ await this._updateSubscriptions();
7417
+ }
7418
+ })();
7419
+ break;
7239
7420
 
7240
- this._subscribe(sub, 'rootSubscribe', []);
7241
- }
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
+ }
7242
7467
 
7243
- for (let id of logsKeys) {
7244
- const sub = this._logsSubscriptions[id];
7245
- let filter;
7468
+ this._subscriptionsByHash[hash] = { ...subscription,
7469
+ state: 'unsubscribed'
7470
+ };
7471
+ await this._updateSubscriptions();
7472
+ })();
7473
+ }
7246
7474
 
7247
- if (typeof sub.filter === 'object') {
7248
- filter = {
7249
- mentions: [sub.filter.toString()]
7250
- };
7251
- } else {
7252
- filter = sub.filter;
7475
+ break;
7253
7476
  }
7477
+ }));
7478
+ }
7479
+ /**
7480
+ * @internal
7481
+ */
7482
+
7483
+
7484
+ _handleServerNotification(serverSubscriptionId, callbackArgs) {
7485
+ const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
7254
7486
 
7255
- this._subscribe(sub, 'logsSubscribe', this._buildArgs([filter], sub.commitment));
7487
+ if (callbacks === undefined) {
7488
+ return;
7256
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
+ });
7257
7502
  }
7258
7503
  /**
7259
7504
  * @internal
@@ -7261,14 +7506,71 @@ class Connection {
7261
7506
 
7262
7507
 
7263
7508
  _wsOnAccountNotification(notification) {
7264
- const res = superstruct.create(notification, AccountNotificationResult);
7509
+ const {
7510
+ result,
7511
+ subscription
7512
+ } = superstruct.create(notification, AccountNotificationResult);
7265
7513
 
7266
- for (const sub of Object.values(this._accountChangeSubscriptions)) {
7267
- if (sub.subscriptionId === res.subscription) {
7268
- sub.callback(res.result.value, res.result.context);
7269
- return;
7270
- }
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);
7271
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;
7272
7574
  }
7273
7575
  /**
7274
7576
  * Register a callback to be invoked whenever the specified account changes
@@ -7281,35 +7583,24 @@ class Connection {
7281
7583
 
7282
7584
 
7283
7585
  onAccountChange(publicKey, callback, commitment) {
7284
- const id = ++this._accountChangeSubscriptionCounter;
7285
- this._accountChangeSubscriptions[id] = {
7286
- publicKey: publicKey.toBase58(),
7287
- callback,
7288
- commitment,
7289
- subscriptionId: null
7290
- };
7291
-
7292
- this._updateSubscriptions();
7586
+ const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
7587
+ 'base64');
7293
7588
 
7294
- return id;
7589
+ return this._makeSubscription({
7590
+ callback,
7591
+ method: 'accountSubscribe',
7592
+ unsubscribeMethod: 'accountUnsubscribe'
7593
+ }, args);
7295
7594
  }
7296
7595
  /**
7297
7596
  * Deregister an account notification callback
7298
7597
  *
7299
- * @param id subscription id to deregister
7598
+ * @param id client subscription id to deregister
7300
7599
  */
7301
7600
 
7302
7601
 
7303
- async removeAccountChangeListener(id) {
7304
- if (this._accountChangeSubscriptions[id]) {
7305
- const subInfo = this._accountChangeSubscriptions[id];
7306
- delete this._accountChangeSubscriptions[id];
7307
- await this._unsubscribe(subInfo, 'accountUnsubscribe');
7308
-
7309
- this._updateSubscriptions();
7310
- } else {
7311
- console.warn(createSubscriptionWarningMessage(id, 'account change'));
7312
- }
7602
+ async removeAccountChangeListener(clientSubscriptionId) {
7603
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change');
7313
7604
  }
7314
7605
  /**
7315
7606
  * @internal
@@ -7317,21 +7608,15 @@ class Connection {
7317
7608
 
7318
7609
 
7319
7610
  _wsOnProgramAccountNotification(notification) {
7320
- const res = superstruct.create(notification, ProgramAccountNotificationResult);
7611
+ const {
7612
+ result,
7613
+ subscription
7614
+ } = superstruct.create(notification, ProgramAccountNotificationResult);
7321
7615
 
7322
- for (const sub of Object.values(this._programAccountChangeSubscriptions)) {
7323
- if (sub.subscriptionId === res.subscription) {
7324
- const {
7325
- value,
7326
- context
7327
- } = res.result;
7328
- sub.callback({
7329
- accountId: value.pubkey,
7330
- accountInfo: value.account
7331
- }, context);
7332
- return;
7333
- }
7334
- }
7616
+ this._handleServerNotification(subscription, [{
7617
+ accountId: result.value.pubkey,
7618
+ accountInfo: result.value.account
7619
+ }, result.context]);
7335
7620
  }
7336
7621
  /**
7337
7622
  * Register a callback to be invoked whenever accounts owned by the
@@ -7346,36 +7631,30 @@ class Connection {
7346
7631
 
7347
7632
 
7348
7633
  onProgramAccountChange(programId, callback, commitment, filters) {
7349
- const id = ++this._programAccountChangeSubscriptionCounter;
7350
- this._programAccountChangeSubscriptions[id] = {
7351
- 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({
7352
7644
  callback,
7353
- commitment,
7354
- subscriptionId: null,
7355
- filters
7356
- };
7357
-
7358
- this._updateSubscriptions();
7359
-
7360
- return id;
7645
+ method: 'programSubscribe',
7646
+ unsubscribeMethod: 'programUnsubscribe'
7647
+ }, args);
7361
7648
  }
7362
7649
  /**
7363
7650
  * Deregister an account notification callback
7364
7651
  *
7365
- * @param id subscription id to deregister
7652
+ * @param id client subscription id to deregister
7366
7653
  */
7367
7654
 
7368
7655
 
7369
- async removeProgramAccountChangeListener(id) {
7370
- if (this._programAccountChangeSubscriptions[id]) {
7371
- const subInfo = this._programAccountChangeSubscriptions[id];
7372
- delete this._programAccountChangeSubscriptions[id];
7373
- await this._unsubscribe(subInfo, 'programUnsubscribe');
7374
-
7375
- this._updateSubscriptions();
7376
- } else {
7377
- console.warn(createSubscriptionWarningMessage(id, 'program account change'));
7378
- }
7656
+ async removeProgramAccountChangeListener(clientSubscriptionId) {
7657
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change');
7379
7658
  }
7380
7659
  /**
7381
7660
  * Registers a callback to be invoked whenever logs are emitted.
@@ -7383,35 +7662,26 @@ class Connection {
7383
7662
 
7384
7663
 
7385
7664
  onLogs(filter, callback, commitment) {
7386
- const id = ++this._logsSubscriptionCounter;
7387
- this._logsSubscriptions[id] = {
7388
- filter,
7389
- callback,
7390
- commitment,
7391
- subscriptionId: null
7392
- };
7665
+ const args = this._buildArgs([typeof filter === 'object' ? {
7666
+ mentions: [filter.toString()]
7667
+ } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default.
7668
+ );
7393
7669
 
7394
- this._updateSubscriptions();
7395
-
7396
- return id;
7670
+ return this._makeSubscription({
7671
+ callback,
7672
+ method: 'logsSubscribe',
7673
+ unsubscribeMethod: 'logsUnsubscribe'
7674
+ }, args);
7397
7675
  }
7398
7676
  /**
7399
7677
  * Deregister a logs callback.
7400
7678
  *
7401
- * @param id subscription id to deregister.
7679
+ * @param id client subscription id to deregister.
7402
7680
  */
7403
7681
 
7404
7682
 
7405
- async removeOnLogsListener(id) {
7406
- if (this._logsSubscriptions[id]) {
7407
- const subInfo = this._logsSubscriptions[id];
7408
- delete this._logsSubscriptions[id];
7409
- await this._unsubscribe(subInfo, 'logsUnsubscribe');
7410
-
7411
- this._updateSubscriptions();
7412
- } else {
7413
- console.warn(createSubscriptionWarningMessage(id, 'logs'));
7414
- }
7683
+ async removeOnLogsListener(clientSubscriptionId) {
7684
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs');
7415
7685
  }
7416
7686
  /**
7417
7687
  * @internal
@@ -7419,17 +7689,12 @@ class Connection {
7419
7689
 
7420
7690
 
7421
7691
  _wsOnLogsNotification(notification) {
7422
- const res = superstruct.create(notification, LogsNotificationResult);
7423
- const keys = Object.keys(this._logsSubscriptions).map(Number);
7424
-
7425
- for (let id of keys) {
7426
- const sub = this._logsSubscriptions[id];
7692
+ const {
7693
+ result,
7694
+ subscription
7695
+ } = superstruct.create(notification, LogsNotificationResult);
7427
7696
 
7428
- if (sub.subscriptionId === res.subscription) {
7429
- sub.callback(res.result.value, res.result.context);
7430
- return;
7431
- }
7432
- }
7697
+ this._handleServerNotification(subscription, [result.value, result.context]);
7433
7698
  }
7434
7699
  /**
7435
7700
  * @internal
@@ -7437,14 +7702,12 @@ class Connection {
7437
7702
 
7438
7703
 
7439
7704
  _wsOnSlotNotification(notification) {
7440
- const res = superstruct.create(notification, SlotNotificationResult);
7705
+ const {
7706
+ result,
7707
+ subscription
7708
+ } = superstruct.create(notification, SlotNotificationResult);
7441
7709
 
7442
- for (const sub of Object.values(this._slotSubscriptions)) {
7443
- if (sub.subscriptionId === res.subscription) {
7444
- sub.callback(res.result);
7445
- return;
7446
- }
7447
- }
7710
+ this._handleServerNotification(subscription, [result]);
7448
7711
  }
7449
7712
  /**
7450
7713
  * Register a callback to be invoked upon slot changes
@@ -7455,33 +7718,23 @@ class Connection {
7455
7718
 
7456
7719
 
7457
7720
  onSlotChange(callback) {
7458
- const id = ++this._slotSubscriptionCounter;
7459
- this._slotSubscriptions[id] = {
7721
+ return this._makeSubscription({
7460
7722
  callback,
7461
- subscriptionId: null
7462
- };
7463
-
7464
- this._updateSubscriptions();
7465
-
7466
- return id;
7723
+ method: 'slotSubscribe',
7724
+ unsubscribeMethod: 'slotUnsubscribe'
7725
+ }, []
7726
+ /* args */
7727
+ );
7467
7728
  }
7468
7729
  /**
7469
7730
  * Deregister a slot notification callback
7470
7731
  *
7471
- * @param id subscription id to deregister
7732
+ * @param id client subscription id to deregister
7472
7733
  */
7473
7734
 
7474
7735
 
7475
- async removeSlotChangeListener(id) {
7476
- if (this._slotSubscriptions[id]) {
7477
- const subInfo = this._slotSubscriptions[id];
7478
- delete this._slotSubscriptions[id];
7479
- await this._unsubscribe(subInfo, 'slotUnsubscribe');
7480
-
7481
- this._updateSubscriptions();
7482
- } else {
7483
- console.warn(createSubscriptionWarningMessage(id, 'slot change'));
7484
- }
7736
+ async removeSlotChangeListener(clientSubscriptionId) {
7737
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change');
7485
7738
  }
7486
7739
  /**
7487
7740
  * @internal
@@ -7489,14 +7742,12 @@ class Connection {
7489
7742
 
7490
7743
 
7491
7744
  _wsOnSlotUpdatesNotification(notification) {
7492
- const res = superstruct.create(notification, SlotUpdateNotificationResult);
7745
+ const {
7746
+ result,
7747
+ subscription
7748
+ } = superstruct.create(notification, SlotUpdateNotificationResult);
7493
7749
 
7494
- for (const sub of Object.values(this._slotUpdateSubscriptions)) {
7495
- if (sub.subscriptionId === res.subscription) {
7496
- sub.callback(res.result);
7497
- return;
7498
- }
7499
- }
7750
+ this._handleServerNotification(subscription, [result]);
7500
7751
  }
7501
7752
  /**
7502
7753
  * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
@@ -7508,32 +7759,36 @@ class Connection {
7508
7759
 
7509
7760
 
7510
7761
  onSlotUpdate(callback) {
7511
- const id = ++this._slotUpdateSubscriptionCounter;
7512
- this._slotUpdateSubscriptions[id] = {
7762
+ return this._makeSubscription({
7513
7763
  callback,
7514
- subscriptionId: null
7515
- };
7516
-
7517
- this._updateSubscriptions();
7518
-
7519
- return id;
7764
+ method: 'slotsUpdatesSubscribe',
7765
+ unsubscribeMethod: 'slotsUpdatesUnsubscribe'
7766
+ }, []
7767
+ /* args */
7768
+ );
7520
7769
  }
7521
7770
  /**
7522
7771
  * Deregister a slot update notification callback
7523
7772
  *
7524
- * @param id subscription id to deregister
7773
+ * @param id client subscription id to deregister
7525
7774
  */
7526
7775
 
7527
7776
 
7528
- async removeSlotUpdateListener(id) {
7529
- if (this._slotUpdateSubscriptions[id]) {
7530
- const subInfo = this._slotUpdateSubscriptions[id];
7531
- delete this._slotUpdateSubscriptions[id];
7532
- await this._unsubscribe(subInfo, 'slotsUpdatesUnsubscribe');
7777
+ async removeSlotUpdateListener(clientSubscriptionId) {
7778
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update');
7779
+ }
7780
+ /**
7781
+ * @internal
7782
+ */
7533
7783
 
7534
- this._updateSubscriptions();
7784
+
7785
+ async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) {
7786
+ const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
7787
+
7788
+ if (dispose) {
7789
+ await dispose();
7535
7790
  } else {
7536
- 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.');
7537
7792
  }
7538
7793
  }
7539
7794
 
@@ -7580,30 +7835,34 @@ class Connection {
7580
7835
 
7581
7836
 
7582
7837
  _wsOnSignatureNotification(notification) {
7583
- const res = superstruct.create(notification, SignatureNotificationResult);
7584
-
7585
- for (const [id, sub] of Object.entries(this._signatureSubscriptions)) {
7586
- if (sub.subscriptionId === res.subscription) {
7587
- if (res.result.value === 'receivedSignature') {
7588
- sub.callback({
7589
- type: 'received'
7590
- }, res.result.context);
7591
- } else {
7592
- // Signatures subscriptions are auto-removed by the RPC service so
7593
- // no need to explicitly send an unsubscribe message
7594
- delete this._signatureSubscriptions[Number(id)];
7595
-
7596
- this._updateSubscriptions();
7597
-
7598
- sub.callback({
7599
- type: 'status',
7600
- result: res.result.value
7601
- }, res.result.context);
7602
- }
7603
-
7604
- return;
7605
- }
7606
- }
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]);
7607
7866
  }
7608
7867
  /**
7609
7868
  * Register a callback to be invoked upon signature updates
@@ -7616,23 +7875,26 @@ class Connection {
7616
7875
 
7617
7876
 
7618
7877
  onSignature(signature, callback, commitment) {
7619
- const id = ++this._signatureSubscriptionCounter;
7620
- this._signatureSubscriptions[id] = {
7621
- signature,
7878
+ const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default.
7879
+ );
7880
+
7881
+ const clientSubscriptionId = this._makeSubscription({
7622
7882
  callback: (notification, context) => {
7623
7883
  if (notification.type === 'status') {
7624
- 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
+ }
7625
7891
  }
7626
7892
  },
7627
- options: {
7628
- commitment
7629
- },
7630
- subscriptionId: null
7631
- };
7893
+ method: 'signatureSubscribe',
7894
+ unsubscribeMethod: 'signatureUnsubscribe'
7895
+ }, args);
7632
7896
 
7633
- this._updateSubscriptions();
7634
-
7635
- return id;
7897
+ return clientSubscriptionId;
7636
7898
  }
7637
7899
  /**
7638
7900
  * Register a callback to be invoked when a transaction is
@@ -7647,35 +7909,43 @@ class Connection {
7647
7909
 
7648
7910
 
7649
7911
  onSignatureWithOptions(signature, callback, options) {
7650
- const id = ++this._signatureSubscriptionCounter;
7651
- this._signatureSubscriptions[id] = {
7652
- signature,
7653
- callback,
7654
- options,
7655
- subscriptionId: null
7912
+ const {
7913
+ commitment,
7914
+ ...extra
7915
+ } = { ...options,
7916
+ commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default.
7917
+
7656
7918
  };
7657
7919
 
7658
- this._updateSubscriptions();
7920
+ const args = this._buildArgs([signature], commitment, undefined
7921
+ /* encoding */
7922
+ , extra);
7659
7923
 
7660
- return id;
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);
7937
+
7938
+ return clientSubscriptionId;
7661
7939
  }
7662
7940
  /**
7663
7941
  * Deregister a signature notification callback
7664
7942
  *
7665
- * @param id subscription id to deregister
7943
+ * @param id client subscription id to deregister
7666
7944
  */
7667
7945
 
7668
7946
 
7669
- async removeSignatureListener(id) {
7670
- if (this._signatureSubscriptions[id]) {
7671
- const subInfo = this._signatureSubscriptions[id];
7672
- delete this._signatureSubscriptions[id];
7673
- await this._unsubscribe(subInfo, 'signatureUnsubscribe');
7674
-
7675
- this._updateSubscriptions();
7676
- } else {
7677
- console.warn(createSubscriptionWarningMessage(id, 'signature result'));
7678
- }
7947
+ async removeSignatureListener(clientSubscriptionId) {
7948
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result');
7679
7949
  }
7680
7950
  /**
7681
7951
  * @internal
@@ -7683,14 +7953,12 @@ class Connection {
7683
7953
 
7684
7954
 
7685
7955
  _wsOnRootNotification(notification) {
7686
- const res = superstruct.create(notification, RootNotificationResult);
7956
+ const {
7957
+ result,
7958
+ subscription
7959
+ } = superstruct.create(notification, RootNotificationResult);
7687
7960
 
7688
- for (const sub of Object.values(this._rootSubscriptions)) {
7689
- if (sub.subscriptionId === res.subscription) {
7690
- sub.callback(res.result);
7691
- return;
7692
- }
7693
- }
7961
+ this._handleServerNotification(subscription, [result]);
7694
7962
  }
7695
7963
  /**
7696
7964
  * Register a callback to be invoked upon root changes
@@ -7701,33 +7969,23 @@ class Connection {
7701
7969
 
7702
7970
 
7703
7971
  onRootChange(callback) {
7704
- const id = ++this._rootSubscriptionCounter;
7705
- this._rootSubscriptions[id] = {
7972
+ return this._makeSubscription({
7706
7973
  callback,
7707
- subscriptionId: null
7708
- };
7709
-
7710
- this._updateSubscriptions();
7711
-
7712
- return id;
7974
+ method: 'rootSubscribe',
7975
+ unsubscribeMethod: 'rootUnsubscribe'
7976
+ }, []
7977
+ /* args */
7978
+ );
7713
7979
  }
7714
7980
  /**
7715
7981
  * Deregister a root notification callback
7716
7982
  *
7717
- * @param id subscription id to deregister
7983
+ * @param id client subscription id to deregister
7718
7984
  */
7719
7985
 
7720
7986
 
7721
- async removeRootChangeListener(id) {
7722
- if (this._rootSubscriptions[id]) {
7723
- const subInfo = this._rootSubscriptions[id];
7724
- delete this._rootSubscriptions[id];
7725
- await this._unsubscribe(subInfo, 'rootUnsubscribe');
7726
-
7727
- this._updateSubscriptions();
7728
- } else {
7729
- console.warn(createSubscriptionWarningMessage(id, 'root change'));
7730
- }
7987
+ async removeRootChangeListener(clientSubscriptionId) {
7988
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change');
7731
7989
  }
7732
7990
 
7733
7991
  }
@@ -9413,6 +9671,9 @@ exports.BLOCKHASH_CACHE_TIMEOUT_MS = BLOCKHASH_CACHE_TIMEOUT_MS;
9413
9671
  exports.BPF_LOADER_DEPRECATED_PROGRAM_ID = BPF_LOADER_DEPRECATED_PROGRAM_ID;
9414
9672
  exports.BPF_LOADER_PROGRAM_ID = BPF_LOADER_PROGRAM_ID;
9415
9673
  exports.BpfLoader = BpfLoader;
9674
+ exports.COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS;
9675
+ exports.ComputeBudgetInstruction = ComputeBudgetInstruction;
9676
+ exports.ComputeBudgetProgram = ComputeBudgetProgram;
9416
9677
  exports.Connection = Connection;
9417
9678
  exports.Ed25519Program = Ed25519Program;
9418
9679
  exports.Enum = Enum;
@@ -9426,7 +9687,6 @@ exports.MAX_SEED_LENGTH = MAX_SEED_LENGTH;
9426
9687
  exports.Message = Message;
9427
9688
  exports.NONCE_ACCOUNT_LENGTH = NONCE_ACCOUNT_LENGTH;
9428
9689
  exports.NonceAccount = NonceAccount;
9429
- exports.PACKET_DATA_SIZE = PACKET_DATA_SIZE;
9430
9690
  exports.PublicKey = PublicKey;
9431
9691
  exports.SOLANA_SCHEMA = SOLANA_SCHEMA;
9432
9692
  exports.STAKE_CONFIG_ID = STAKE_CONFIG_ID;