@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.
@@ -2076,6 +2076,16 @@ class Account {
2076
2076
 
2077
2077
  const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey('BPFLoader1111111111111111111111111111111111');
2078
2078
 
2079
+ /**
2080
+ * Maximum over-the-wire size of a Transaction
2081
+ *
2082
+ * 1280 is IPv6 minimum MTU
2083
+ * 40 bytes is the size of the IPv6 header
2084
+ * 8 bytes is the size of the fragment header
2085
+ */
2086
+ const PACKET_DATA_SIZE = 1280 - 40 - 8;
2087
+ const SIGNATURE_LENGTH_IN_BYTES = 64;
2088
+
2079
2089
  /**
2080
2090
  * Layout for a public key
2081
2091
  */
@@ -2335,20 +2345,8 @@ function assert (condition, message) {
2335
2345
 
2336
2346
  /**
2337
2347
  * Default (empty) signature
2338
- *
2339
- * Signatures are 64 bytes in length
2340
2348
  */
2341
- const DEFAULT_SIGNATURE = Buffer.alloc(64).fill(0);
2342
- /**
2343
- * Maximum over-the-wire size of a Transaction
2344
- *
2345
- * 1280 is IPv6 minimum MTU
2346
- * 40 bytes is the size of the IPv6 header
2347
- * 8 bytes is the size of the fragment header
2348
- */
2349
-
2350
- const PACKET_DATA_SIZE = 1280 - 40 - 8;
2351
- const SIGNATURE_LENGTH = 64;
2349
+ const DEFAULT_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0);
2352
2350
  /**
2353
2351
  * Account metadata used to define instructions
2354
2352
  */
@@ -2978,8 +2976,8 @@ class Transaction {
2978
2976
  let signatures = [];
2979
2977
 
2980
2978
  for (let i = 0; i < signatureCount; i++) {
2981
- const signature = byteArray.slice(0, SIGNATURE_LENGTH);
2982
- byteArray = byteArray.slice(SIGNATURE_LENGTH);
2979
+ const signature = byteArray.slice(0, SIGNATURE_LENGTH_IN_BYTES);
2980
+ byteArray = byteArray.slice(SIGNATURE_LENGTH_IN_BYTES);
2983
2981
  signatures.push(bs58.encode(Buffer.from(signature)));
2984
2982
  }
2985
2983
 
@@ -3868,11 +3866,11 @@ class SystemProgram {
3868
3866
  }
3869
3867
  SystemProgram.programId = new PublicKey('11111111111111111111111111111111');
3870
3868
 
3871
- // Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the
3872
3869
  // rest of the Transaction fields
3873
3870
  //
3874
3871
  // TODO: replace 300 with a proper constant for the size of the other
3875
3872
  // Transaction fields
3873
+
3876
3874
  const CHUNK_SIZE = PACKET_DATA_SIZE - 300;
3877
3875
  /**
3878
3876
  * Program loader interface
@@ -4072,6 +4070,136 @@ class BpfLoader {
4072
4070
 
4073
4071
  }
4074
4072
 
4073
+ /**
4074
+ * Compute Budget Instruction class
4075
+ */
4076
+
4077
+ class ComputeBudgetInstruction {
4078
+ /**
4079
+ * @internal
4080
+ */
4081
+ constructor() {}
4082
+ /**
4083
+ * Decode a compute budget instruction and retrieve the instruction type.
4084
+ */
4085
+
4086
+
4087
+ static decodeInstructionType(instruction) {
4088
+ this.checkProgramId(instruction.programId);
4089
+ const instructionTypeLayout = BufferLayout.u8('instruction');
4090
+ const typeIndex = instructionTypeLayout.decode(instruction.data);
4091
+ let type;
4092
+
4093
+ for (const [ixType, layout] of Object.entries(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS)) {
4094
+ if (layout.index == typeIndex) {
4095
+ type = ixType;
4096
+ break;
4097
+ }
4098
+ }
4099
+
4100
+ if (!type) {
4101
+ throw new Error('Instruction type incorrect; not a ComputeBudgetInstruction');
4102
+ }
4103
+
4104
+ return type;
4105
+ }
4106
+ /**
4107
+ * Decode request units compute budget instruction and retrieve the instruction params.
4108
+ */
4109
+
4110
+
4111
+ static decodeRequestUnits(instruction) {
4112
+ this.checkProgramId(instruction.programId);
4113
+ const {
4114
+ units,
4115
+ additionalFee
4116
+ } = decodeData(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits, instruction.data);
4117
+ return {
4118
+ units,
4119
+ additionalFee
4120
+ };
4121
+ }
4122
+ /**
4123
+ * Decode request heap frame compute budget instruction and retrieve the instruction params.
4124
+ */
4125
+
4126
+
4127
+ static decodeRequestHeapFrame(instruction) {
4128
+ this.checkProgramId(instruction.programId);
4129
+ const {
4130
+ bytes
4131
+ } = decodeData(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame, instruction.data);
4132
+ return {
4133
+ bytes
4134
+ };
4135
+ }
4136
+ /**
4137
+ * @internal
4138
+ */
4139
+
4140
+
4141
+ static checkProgramId(programId) {
4142
+ if (!programId.equals(ComputeBudgetProgram.programId)) {
4143
+ throw new Error('invalid instruction; programId is not ComputeBudgetProgram');
4144
+ }
4145
+ }
4146
+
4147
+ }
4148
+ /**
4149
+ * An enumeration of valid ComputeBudgetInstructionType's
4150
+ */
4151
+
4152
+ /**
4153
+ * An enumeration of valid ComputeBudget InstructionType's
4154
+ * @internal
4155
+ */
4156
+ const COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = Object.freeze({
4157
+ RequestUnits: {
4158
+ index: 0,
4159
+ layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('units'), BufferLayout.u32('additionalFee')])
4160
+ },
4161
+ RequestHeapFrame: {
4162
+ index: 1,
4163
+ layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('bytes')])
4164
+ }
4165
+ });
4166
+ /**
4167
+ * Factory class for transaction instructions to interact with the Compute Budget program
4168
+ */
4169
+
4170
+ class ComputeBudgetProgram {
4171
+ /**
4172
+ * @internal
4173
+ */
4174
+ constructor() {}
4175
+ /**
4176
+ * Public key that identifies the Compute Budget program
4177
+ */
4178
+
4179
+
4180
+ static requestUnits(params) {
4181
+ const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits;
4182
+ const data = encodeData(type, params);
4183
+ return new TransactionInstruction({
4184
+ keys: [],
4185
+ programId: this.programId,
4186
+ data
4187
+ });
4188
+ }
4189
+
4190
+ static requestHeapFrame(params) {
4191
+ const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame;
4192
+ const data = encodeData(type, params);
4193
+ return new TransactionInstruction({
4194
+ keys: [],
4195
+ programId: this.programId,
4196
+ data
4197
+ });
4198
+ }
4199
+
4200
+ }
4201
+ ComputeBudgetProgram.programId = new PublicKey('ComputeBudget111111111111111111111111111111');
4202
+
4075
4203
  var browserPonyfill = {exports: {}};
4076
4204
 
4077
4205
  (function (module, exports) {
@@ -4633,6 +4761,82 @@ module.exports = exports;
4633
4761
 
4634
4762
  var crossFetch = /*@__PURE__*/getDefaultExportFromCjs(browserPonyfill.exports);
4635
4763
 
4764
+ var objToString = Object.prototype.toString;
4765
+ var objKeys = Object.keys || function(obj) {
4766
+ var keys = [];
4767
+ for (var name in obj) {
4768
+ keys.push(name);
4769
+ }
4770
+ return keys;
4771
+ };
4772
+
4773
+ function stringify(val, isArrayProp) {
4774
+ var i, max, str, keys, key, propVal, toStr;
4775
+ if (val === true) {
4776
+ return "true";
4777
+ }
4778
+ if (val === false) {
4779
+ return "false";
4780
+ }
4781
+ switch (typeof val) {
4782
+ case "object":
4783
+ if (val === null) {
4784
+ return null;
4785
+ } else if (val.toJSON && typeof val.toJSON === "function") {
4786
+ return stringify(val.toJSON(), isArrayProp);
4787
+ } else {
4788
+ toStr = objToString.call(val);
4789
+ if (toStr === "[object Array]") {
4790
+ str = '[';
4791
+ max = val.length - 1;
4792
+ for(i = 0; i < max; i++) {
4793
+ str += stringify(val[i], true) + ',';
4794
+ }
4795
+ if (max > -1) {
4796
+ str += stringify(val[i], true);
4797
+ }
4798
+ return str + ']';
4799
+ } else if (toStr === "[object Object]") {
4800
+ // only object is left
4801
+ keys = objKeys(val).sort();
4802
+ max = keys.length;
4803
+ str = "";
4804
+ i = 0;
4805
+ while (i < max) {
4806
+ key = keys[i];
4807
+ propVal = stringify(val[key], false);
4808
+ if (propVal !== undefined) {
4809
+ if (str) {
4810
+ str += ',';
4811
+ }
4812
+ str += JSON.stringify(key) + ':' + propVal;
4813
+ }
4814
+ i++;
4815
+ }
4816
+ return '{' + str + '}';
4817
+ } else {
4818
+ return JSON.stringify(val);
4819
+ }
4820
+ }
4821
+ case "function":
4822
+ case "undefined":
4823
+ return isArrayProp ? null : undefined;
4824
+ case "string":
4825
+ return JSON.stringify(val);
4826
+ default:
4827
+ return isFinite(val) ? val : null;
4828
+ }
4829
+ }
4830
+
4831
+ var fastStableStringify = function(val) {
4832
+ var returnVal = stringify(val, false);
4833
+ if (returnVal !== undefined) {
4834
+ return ''+ returnVal;
4835
+ }
4836
+ };
4837
+
4838
+ var fastStableStringify$1 = fastStableStringify;
4839
+
4636
4840
  const MINIMUM_SLOT_PER_EPOCH = 32; // Returns the number of trailing zeros in the binary representation of self.
4637
4841
 
4638
4842
  function trailingZeros(n) {
@@ -4799,6 +5003,12 @@ const BufferFromRawAccountData = coerce(instance(Buffer), RawAccountDataResult,
4799
5003
  */
4800
5004
 
4801
5005
  const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
5006
+ /**
5007
+ * HACK.
5008
+ * Copied from rpc-websockets/dist/lib/client.
5009
+ * Otherwise, `yarn build` fails with:
5010
+ * https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d
5011
+ */
4802
5012
 
4803
5013
  /**
4804
5014
  * @internal
@@ -5667,14 +5877,9 @@ const LogsNotificationResult = type({
5667
5877
  * Filter for log subscriptions.
5668
5878
  */
5669
5879
 
5670
- function createSubscriptionWarningMessage(id, label) {
5671
- return 'Ignored unsubscribe request because an active subscription ' + `with id \`${id}\` for '${label}' events could not be found.`;
5672
- }
5673
5880
  /**
5674
5881
  * A connection to a fullnode JSON RPC endpoint
5675
5882
  */
5676
-
5677
-
5678
5883
  class Connection {
5679
5884
  /** @internal */
5680
5885
 
@@ -5698,21 +5903,13 @@ class Connection {
5698
5903
 
5699
5904
  /** @internal */
5700
5905
 
5701
- /** @internal */
5702
-
5703
- /** @internal */
5704
-
5705
- /** @internal */
5706
-
5707
- /** @internal */
5708
-
5709
- /** @internal */
5710
-
5711
- /** @internal */
5712
-
5713
- /** @internal */
5714
-
5715
- /** @internal */
5906
+ /** @internal
5907
+ * A number that we increment every time an active connection closes.
5908
+ * Used to determine whether the same socket connection that was open
5909
+ * when an async operation started is the same one that's active when
5910
+ * its continuation fires.
5911
+ *
5912
+ */
5716
5913
 
5717
5914
  /** @internal */
5718
5915
 
@@ -5728,7 +5925,19 @@ class Connection {
5728
5925
 
5729
5926
  /** @internal */
5730
5927
 
5731
- /** @internal */
5928
+ /**
5929
+ * Special case.
5930
+ * After a signature is processed, RPCs automatically dispose of the
5931
+ * subscription on the server side. We need to track which of these
5932
+ * subscriptions have been disposed in such a way, so that we know
5933
+ * whether the client is dealing with a not-yet-processed signature
5934
+ * (in which case we must tear down the server subscription) or an
5935
+ * already-processed signature (in which case the client can simply
5936
+ * clear out the subscription locally without telling the server).
5937
+ *
5938
+ * NOTE: There is a proposal to eliminate this special case, here:
5939
+ * https://github.com/solana-labs/solana/issues/18892
5940
+ */
5732
5941
 
5733
5942
  /** @internal */
5734
5943
 
@@ -5750,6 +5959,7 @@ class Connection {
5750
5959
  this._rpcWebSocketConnected = false;
5751
5960
  this._rpcWebSocketHeartbeat = null;
5752
5961
  this._rpcWebSocketIdleTimeout = null;
5962
+ this._rpcWebSocketGeneration = 0;
5753
5963
  this._disableBlockhashCaching = false;
5754
5964
  this._pollingBlockhash = false;
5755
5965
  this._blockhashInfo = {
@@ -5758,20 +5968,11 @@ class Connection {
5758
5968
  transactionSignatures: [],
5759
5969
  simulatedSignatures: []
5760
5970
  };
5761
- this._accountChangeSubscriptionCounter = 0;
5762
- this._accountChangeSubscriptions = {};
5763
- this._programAccountChangeSubscriptionCounter = 0;
5764
- this._programAccountChangeSubscriptions = {};
5765
- this._rootSubscriptionCounter = 0;
5766
- this._rootSubscriptions = {};
5767
- this._signatureSubscriptionCounter = 0;
5768
- this._signatureSubscriptions = {};
5769
- this._slotSubscriptionCounter = 0;
5770
- this._slotSubscriptions = {};
5771
- this._logsSubscriptionCounter = 0;
5772
- this._logsSubscriptions = {};
5773
- this._slotUpdateSubscriptionCounter = 0;
5774
- this._slotUpdateSubscriptions = {};
5971
+ this._nextClientSubscriptionId = 0;
5972
+ this._subscriptionDisposeFunctionsByClientSubscriptionId = {};
5973
+ this._subscriptionCallbacksByServerSubscriptionId = {};
5974
+ this._subscriptionsByHash = {};
5975
+ this._subscriptionsAutoDisposedByRpc = new Set();
5775
5976
  let url = new URL(endpoint);
5776
5977
  const useHttps = url.protocol === 'https:';
5777
5978
  let wsEndpoint;
@@ -7539,6 +7740,8 @@ class Connection {
7539
7740
 
7540
7741
 
7541
7742
  _wsOnClose(code) {
7743
+ this._rpcWebSocketGeneration++;
7744
+
7542
7745
  if (this._rpcWebSocketHeartbeat) {
7543
7746
  clearInterval(this._rpcWebSocketHeartbeat);
7544
7747
  this._rpcWebSocketHeartbeat = null;
@@ -7552,85 +7755,20 @@ class Connection {
7552
7755
  } // implicit close, prepare subscriptions for auto-reconnect
7553
7756
 
7554
7757
 
7555
- this._resetSubscriptions();
7556
- }
7557
- /**
7558
- * @internal
7559
- */
7560
-
7561
-
7562
- async _subscribe(sub, rpcMethod, rpcArgs) {
7563
- if (sub.subscriptionId == null) {
7564
- sub.subscriptionId = 'subscribing';
7565
-
7566
- try {
7567
- const id = await this._rpcWebSocket.call(rpcMethod, rpcArgs);
7568
-
7569
- if (typeof id === 'number' && sub.subscriptionId === 'subscribing') {
7570
- // eslint-disable-next-line require-atomic-updates
7571
- sub.subscriptionId = id;
7572
- }
7573
- } catch (err) {
7574
- if (sub.subscriptionId === 'subscribing') {
7575
- // eslint-disable-next-line require-atomic-updates
7576
- sub.subscriptionId = null;
7577
- }
7578
-
7579
- if (err instanceof Error) {
7580
- console.error(`${rpcMethod} error for argument`, rpcArgs, err.message);
7581
- }
7582
- }
7583
- }
7584
- }
7585
- /**
7586
- * @internal
7587
- */
7588
-
7589
-
7590
- async _unsubscribe(sub, rpcMethod) {
7591
- const subscriptionId = sub.subscriptionId;
7592
-
7593
- if (subscriptionId != null && typeof subscriptionId != 'string') {
7594
- const unsubscribeId = subscriptionId;
7595
-
7596
- try {
7597
- await this._rpcWebSocket.call(rpcMethod, [unsubscribeId]);
7598
- } catch (err) {
7599
- if (err instanceof Error) {
7600
- console.error(`${rpcMethod} error:`, err.message);
7601
- }
7602
- }
7603
- }
7604
- }
7605
- /**
7606
- * @internal
7607
- */
7608
-
7609
-
7610
- _resetSubscriptions() {
7611
- Object.values(this._accountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7612
- Object.values(this._logsSubscriptions).forEach(s => s.subscriptionId = null);
7613
- Object.values(this._programAccountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7614
- Object.values(this._rootSubscriptions).forEach(s => s.subscriptionId = null);
7615
- Object.values(this._signatureSubscriptions).forEach(s => s.subscriptionId = null);
7616
- Object.values(this._slotSubscriptions).forEach(s => s.subscriptionId = null);
7617
- Object.values(this._slotUpdateSubscriptions).forEach(s => s.subscriptionId = null);
7758
+ this._subscriptionCallbacksByServerSubscriptionId = {};
7759
+ Object.entries(this._subscriptionsByHash).forEach(([hash, subscription]) => {
7760
+ this._subscriptionsByHash[hash] = { ...subscription,
7761
+ state: 'pending'
7762
+ };
7763
+ });
7618
7764
  }
7619
7765
  /**
7620
7766
  * @internal
7621
7767
  */
7622
7768
 
7623
7769
 
7624
- _updateSubscriptions() {
7625
- const accountKeys = Object.keys(this._accountChangeSubscriptions).map(Number);
7626
- const programKeys = Object.keys(this._programAccountChangeSubscriptions).map(Number);
7627
- const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
7628
- const slotUpdateKeys = Object.keys(this._slotUpdateSubscriptions).map(Number);
7629
- const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
7630
- const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
7631
- const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
7632
-
7633
- if (accountKeys.length === 0 && programKeys.length === 0 && slotKeys.length === 0 && slotUpdateKeys.length === 0 && signatureKeys.length === 0 && rootKeys.length === 0 && logsKeys.length === 0) {
7770
+ async _updateSubscriptions() {
7771
+ if (Object.keys(this._subscriptionsByHash).length === 0) {
7634
7772
  if (this._rpcWebSocketConnected) {
7635
7773
  this._rpcWebSocketConnected = false;
7636
7774
  this._rpcWebSocketIdleTimeout = setTimeout(() => {
@@ -7662,60 +7800,167 @@ class Connection {
7662
7800
  return;
7663
7801
  }
7664
7802
 
7665
- for (let id of accountKeys) {
7666
- const sub = this._accountChangeSubscriptions[id];
7803
+ const activeWebSocketGeneration = this._rpcWebSocketGeneration;
7667
7804
 
7668
- this._subscribe(sub, 'accountSubscribe', this._buildArgs([sub.publicKey], sub.commitment, 'base64'));
7669
- }
7670
-
7671
- for (let id of programKeys) {
7672
- const sub = this._programAccountChangeSubscriptions[id];
7805
+ const isCurrentConnectionStillActive = () => {
7806
+ return activeWebSocketGeneration === this._rpcWebSocketGeneration;
7807
+ };
7673
7808
 
7674
- this._subscribe(sub, 'programSubscribe', this._buildArgs([sub.programId], sub.commitment, 'base64', {
7675
- filters: sub.filters
7676
- }));
7677
- }
7809
+ await Promise.all( // Don't be tempted to change this to `Object.entries`. We call
7810
+ // `_updateSubscriptions` recursively when processing the state,
7811
+ // so it's important that we look up the *current* version of
7812
+ // each subscription, every time we process a hash.
7813
+ Object.keys(this._subscriptionsByHash).map(async hash => {
7814
+ const subscription = this._subscriptionsByHash[hash];
7678
7815
 
7679
- for (let id of slotKeys) {
7680
- const sub = this._slotSubscriptions[id];
7816
+ if (subscription === undefined) {
7817
+ // This entry has since been deleted. Skip.
7818
+ return;
7819
+ }
7681
7820
 
7682
- this._subscribe(sub, 'slotSubscribe', []);
7683
- }
7821
+ switch (subscription.state) {
7822
+ case 'pending':
7823
+ case 'unsubscribed':
7824
+ if (subscription.callbacks.size === 0) {
7825
+ /**
7826
+ * You can end up here when:
7827
+ *
7828
+ * - a subscription has recently unsubscribed
7829
+ * without having new callbacks added to it
7830
+ * while the unsubscribe was in flight, or
7831
+ * - when a pending subscription has its
7832
+ * listeners removed before a request was
7833
+ * sent to the server.
7834
+ *
7835
+ * Being that nobody is interested in this
7836
+ * subscription any longer, delete it.
7837
+ */
7838
+ delete this._subscriptionsByHash[hash];
7839
+
7840
+ if (subscription.state === 'unsubscribed') {
7841
+ delete this._subscriptionCallbacksByServerSubscriptionId[subscription.serverSubscriptionId];
7842
+ }
7684
7843
 
7685
- for (let id of slotUpdateKeys) {
7686
- const sub = this._slotUpdateSubscriptions[id];
7844
+ await this._updateSubscriptions();
7845
+ return;
7846
+ }
7687
7847
 
7688
- this._subscribe(sub, 'slotsUpdatesSubscribe', []);
7689
- }
7848
+ await (async () => {
7849
+ const {
7850
+ args,
7851
+ method
7852
+ } = subscription;
7690
7853
 
7691
- for (let id of signatureKeys) {
7692
- const sub = this._signatureSubscriptions[id];
7693
- const args = [sub.signature];
7694
- if (sub.options) args.push(sub.options);
7854
+ try {
7855
+ this._subscriptionsByHash[hash] = { ...subscription,
7856
+ state: 'subscribing'
7857
+ };
7858
+ const serverSubscriptionId = await this._rpcWebSocket.call(method, args);
7859
+ this._subscriptionsByHash[hash] = { ...subscription,
7860
+ serverSubscriptionId,
7861
+ state: 'subscribed'
7862
+ };
7863
+ this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId] = subscription.callbacks;
7864
+ await this._updateSubscriptions();
7865
+ } catch (e) {
7866
+ if (e instanceof Error) {
7867
+ console.error(`${method} error for argument`, args, e.message);
7868
+ }
7869
+
7870
+ if (!isCurrentConnectionStillActive()) {
7871
+ return;
7872
+ } // TODO: Maybe add an 'errored' state or a retry limit?
7695
7873
 
7696
- this._subscribe(sub, 'signatureSubscribe', args);
7697
- }
7698
7874
 
7699
- for (let id of rootKeys) {
7700
- const sub = this._rootSubscriptions[id];
7875
+ this._subscriptionsByHash[hash] = { ...subscription,
7876
+ state: 'pending'
7877
+ };
7878
+ await this._updateSubscriptions();
7879
+ }
7880
+ })();
7881
+ break;
7701
7882
 
7702
- this._subscribe(sub, 'rootSubscribe', []);
7703
- }
7883
+ case 'subscribed':
7884
+ if (subscription.callbacks.size === 0) {
7885
+ // By the time we successfully set up a subscription
7886
+ // with the server, the client stopped caring about it.
7887
+ // Tear it down now.
7888
+ await (async () => {
7889
+ const {
7890
+ serverSubscriptionId,
7891
+ unsubscribeMethod
7892
+ } = subscription;
7893
+
7894
+ if (this._subscriptionsAutoDisposedByRpc.has(serverSubscriptionId)) {
7895
+ /**
7896
+ * Special case.
7897
+ * If we're dealing with a subscription that has been auto-
7898
+ * disposed by the RPC, then we can skip the RPC call to
7899
+ * tear down the subscription here.
7900
+ *
7901
+ * NOTE: There is a proposal to eliminate this special case, here:
7902
+ * https://github.com/solana-labs/solana/issues/18892
7903
+ */
7904
+ this._subscriptionsAutoDisposedByRpc.delete(serverSubscriptionId);
7905
+ } else {
7906
+ this._subscriptionsByHash[hash] = { ...subscription,
7907
+ state: 'unsubscribing'
7908
+ };
7909
+
7910
+ try {
7911
+ await this._rpcWebSocket.call(unsubscribeMethod, [serverSubscriptionId]);
7912
+ } catch (e) {
7913
+ if (e instanceof Error) {
7914
+ console.error(`${unsubscribeMethod} error:`, e.message);
7915
+ }
7916
+
7917
+ if (!isCurrentConnectionStillActive()) {
7918
+ return;
7919
+ } // TODO: Maybe add an 'errored' state or a retry limit?
7920
+
7921
+
7922
+ this._subscriptionsByHash[hash] = { ...subscription,
7923
+ state: 'subscribed'
7924
+ };
7925
+ await this._updateSubscriptions();
7926
+ return;
7927
+ }
7928
+ }
7704
7929
 
7705
- for (let id of logsKeys) {
7706
- const sub = this._logsSubscriptions[id];
7707
- let filter;
7930
+ this._subscriptionsByHash[hash] = { ...subscription,
7931
+ state: 'unsubscribed'
7932
+ };
7933
+ await this._updateSubscriptions();
7934
+ })();
7935
+ }
7708
7936
 
7709
- if (typeof sub.filter === 'object') {
7710
- filter = {
7711
- mentions: [sub.filter.toString()]
7712
- };
7713
- } else {
7714
- filter = sub.filter;
7937
+ break;
7715
7938
  }
7939
+ }));
7940
+ }
7941
+ /**
7942
+ * @internal
7943
+ */
7944
+
7945
+
7946
+ _handleServerNotification(serverSubscriptionId, callbackArgs) {
7947
+ const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
7716
7948
 
7717
- this._subscribe(sub, 'logsSubscribe', this._buildArgs([filter], sub.commitment));
7949
+ if (callbacks === undefined) {
7950
+ return;
7718
7951
  }
7952
+
7953
+ callbacks.forEach(cb => {
7954
+ try {
7955
+ cb( // I failed to find a way to convince TypeScript that `cb` is of type
7956
+ // `TCallback` which is certainly compatible with `Parameters<TCallback>`.
7957
+ // See https://github.com/microsoft/TypeScript/issues/47615
7958
+ // @ts-ignore
7959
+ ...callbackArgs);
7960
+ } catch (e) {
7961
+ console.error(e);
7962
+ }
7963
+ });
7719
7964
  }
7720
7965
  /**
7721
7966
  * @internal
@@ -7723,14 +7968,71 @@ class Connection {
7723
7968
 
7724
7969
 
7725
7970
  _wsOnAccountNotification(notification) {
7726
- const res = create(notification, AccountNotificationResult);
7971
+ const {
7972
+ result,
7973
+ subscription
7974
+ } = create(notification, AccountNotificationResult);
7727
7975
 
7728
- for (const sub of Object.values(this._accountChangeSubscriptions)) {
7729
- if (sub.subscriptionId === res.subscription) {
7730
- sub.callback(res.result.value, res.result.context);
7731
- return;
7732
- }
7976
+ this._handleServerNotification(subscription, [result.value, result.context]);
7977
+ }
7978
+ /**
7979
+ * @internal
7980
+ */
7981
+
7982
+
7983
+ _makeSubscription(subscriptionConfig,
7984
+ /**
7985
+ * When preparing `args` for a call to `_makeSubscription`, be sure
7986
+ * to carefully apply a default `commitment` property, if necessary.
7987
+ *
7988
+ * - If the user supplied a `commitment` use that.
7989
+ * - Otherwise, if the `Connection::commitment` is set, use that.
7990
+ * - Otherwise, set it to the RPC server default: `finalized`.
7991
+ *
7992
+ * This is extremely important to ensure that these two fundamentally
7993
+ * identical subscriptions produce the same identifying hash:
7994
+ *
7995
+ * - A subscription made without specifying a commitment.
7996
+ * - A subscription made where the commitment specified is the same
7997
+ * as the default applied to the subscription above.
7998
+ *
7999
+ * Example; these two subscriptions must produce the same hash:
8000
+ *
8001
+ * - An `accountSubscribe` subscription for `'PUBKEY'`
8002
+ * - An `accountSubscribe` subscription for `'PUBKEY'` with commitment
8003
+ * `'finalized'`.
8004
+ *
8005
+ * See the 'making a subscription with defaulted params omitted' test
8006
+ * in `connection-subscriptions.ts` for more.
8007
+ */
8008
+ args) {
8009
+ const clientSubscriptionId = this._nextClientSubscriptionId++;
8010
+ const hash = fastStableStringify$1([subscriptionConfig.method, args], true
8011
+ /* isArrayProp */
8012
+ );
8013
+ const existingSubscription = this._subscriptionsByHash[hash];
8014
+
8015
+ if (existingSubscription === undefined) {
8016
+ this._subscriptionsByHash[hash] = { ...subscriptionConfig,
8017
+ args,
8018
+ callbacks: new Set([subscriptionConfig.callback]),
8019
+ state: 'pending'
8020
+ };
8021
+ } else {
8022
+ existingSubscription.callbacks.add(subscriptionConfig.callback);
7733
8023
  }
8024
+
8025
+ this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId] = async () => {
8026
+ delete this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
8027
+ const subscription = this._subscriptionsByHash[hash];
8028
+ assert(subscription !== undefined, `Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`);
8029
+ subscription.callbacks.delete(subscriptionConfig.callback);
8030
+ await this._updateSubscriptions();
8031
+ };
8032
+
8033
+ this._updateSubscriptions();
8034
+
8035
+ return clientSubscriptionId;
7734
8036
  }
7735
8037
  /**
7736
8038
  * Register a callback to be invoked whenever the specified account changes
@@ -7743,35 +8045,24 @@ class Connection {
7743
8045
 
7744
8046
 
7745
8047
  onAccountChange(publicKey, callback, commitment) {
7746
- const id = ++this._accountChangeSubscriptionCounter;
7747
- this._accountChangeSubscriptions[id] = {
7748
- publicKey: publicKey.toBase58(),
7749
- callback,
7750
- commitment,
7751
- subscriptionId: null
7752
- };
7753
-
7754
- this._updateSubscriptions();
8048
+ const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
8049
+ 'base64');
7755
8050
 
7756
- return id;
8051
+ return this._makeSubscription({
8052
+ callback,
8053
+ method: 'accountSubscribe',
8054
+ unsubscribeMethod: 'accountUnsubscribe'
8055
+ }, args);
7757
8056
  }
7758
8057
  /**
7759
8058
  * Deregister an account notification callback
7760
8059
  *
7761
- * @param id subscription id to deregister
8060
+ * @param id client subscription id to deregister
7762
8061
  */
7763
8062
 
7764
8063
 
7765
- async removeAccountChangeListener(id) {
7766
- if (this._accountChangeSubscriptions[id]) {
7767
- const subInfo = this._accountChangeSubscriptions[id];
7768
- delete this._accountChangeSubscriptions[id];
7769
- await this._unsubscribe(subInfo, 'accountUnsubscribe');
7770
-
7771
- this._updateSubscriptions();
7772
- } else {
7773
- console.warn(createSubscriptionWarningMessage(id, 'account change'));
7774
- }
8064
+ async removeAccountChangeListener(clientSubscriptionId) {
8065
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change');
7775
8066
  }
7776
8067
  /**
7777
8068
  * @internal
@@ -7779,21 +8070,15 @@ class Connection {
7779
8070
 
7780
8071
 
7781
8072
  _wsOnProgramAccountNotification(notification) {
7782
- const res = create(notification, ProgramAccountNotificationResult);
8073
+ const {
8074
+ result,
8075
+ subscription
8076
+ } = create(notification, ProgramAccountNotificationResult);
7783
8077
 
7784
- for (const sub of Object.values(this._programAccountChangeSubscriptions)) {
7785
- if (sub.subscriptionId === res.subscription) {
7786
- const {
7787
- value,
7788
- context
7789
- } = res.result;
7790
- sub.callback({
7791
- accountId: value.pubkey,
7792
- accountInfo: value.account
7793
- }, context);
7794
- return;
7795
- }
7796
- }
8078
+ this._handleServerNotification(subscription, [{
8079
+ accountId: result.value.pubkey,
8080
+ accountInfo: result.value.account
8081
+ }, result.context]);
7797
8082
  }
7798
8083
  /**
7799
8084
  * Register a callback to be invoked whenever accounts owned by the
@@ -7808,36 +8093,30 @@ class Connection {
7808
8093
 
7809
8094
 
7810
8095
  onProgramAccountChange(programId, callback, commitment, filters) {
7811
- const id = ++this._programAccountChangeSubscriptionCounter;
7812
- this._programAccountChangeSubscriptions[id] = {
7813
- programId: programId.toBase58(),
8096
+ const args = this._buildArgs([programId.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
8097
+ 'base64'
8098
+ /* encoding */
8099
+ , filters ? {
8100
+ filters: filters
8101
+ } : undefined
8102
+ /* extra */
8103
+ );
8104
+
8105
+ return this._makeSubscription({
7814
8106
  callback,
7815
- commitment,
7816
- subscriptionId: null,
7817
- filters
7818
- };
7819
-
7820
- this._updateSubscriptions();
7821
-
7822
- return id;
8107
+ method: 'programSubscribe',
8108
+ unsubscribeMethod: 'programUnsubscribe'
8109
+ }, args);
7823
8110
  }
7824
8111
  /**
7825
8112
  * Deregister an account notification callback
7826
8113
  *
7827
- * @param id subscription id to deregister
8114
+ * @param id client subscription id to deregister
7828
8115
  */
7829
8116
 
7830
8117
 
7831
- async removeProgramAccountChangeListener(id) {
7832
- if (this._programAccountChangeSubscriptions[id]) {
7833
- const subInfo = this._programAccountChangeSubscriptions[id];
7834
- delete this._programAccountChangeSubscriptions[id];
7835
- await this._unsubscribe(subInfo, 'programUnsubscribe');
7836
-
7837
- this._updateSubscriptions();
7838
- } else {
7839
- console.warn(createSubscriptionWarningMessage(id, 'program account change'));
7840
- }
8118
+ async removeProgramAccountChangeListener(clientSubscriptionId) {
8119
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change');
7841
8120
  }
7842
8121
  /**
7843
8122
  * Registers a callback to be invoked whenever logs are emitted.
@@ -7845,35 +8124,26 @@ class Connection {
7845
8124
 
7846
8125
 
7847
8126
  onLogs(filter, callback, commitment) {
7848
- const id = ++this._logsSubscriptionCounter;
7849
- this._logsSubscriptions[id] = {
7850
- filter,
7851
- callback,
7852
- commitment,
7853
- subscriptionId: null
7854
- };
8127
+ const args = this._buildArgs([typeof filter === 'object' ? {
8128
+ mentions: [filter.toString()]
8129
+ } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default.
8130
+ );
7855
8131
 
7856
- this._updateSubscriptions();
7857
-
7858
- return id;
8132
+ return this._makeSubscription({
8133
+ callback,
8134
+ method: 'logsSubscribe',
8135
+ unsubscribeMethod: 'logsUnsubscribe'
8136
+ }, args);
7859
8137
  }
7860
8138
  /**
7861
8139
  * Deregister a logs callback.
7862
8140
  *
7863
- * @param id subscription id to deregister.
8141
+ * @param id client subscription id to deregister.
7864
8142
  */
7865
8143
 
7866
8144
 
7867
- async removeOnLogsListener(id) {
7868
- if (this._logsSubscriptions[id]) {
7869
- const subInfo = this._logsSubscriptions[id];
7870
- delete this._logsSubscriptions[id];
7871
- await this._unsubscribe(subInfo, 'logsUnsubscribe');
7872
-
7873
- this._updateSubscriptions();
7874
- } else {
7875
- console.warn(createSubscriptionWarningMessage(id, 'logs'));
7876
- }
8145
+ async removeOnLogsListener(clientSubscriptionId) {
8146
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs');
7877
8147
  }
7878
8148
  /**
7879
8149
  * @internal
@@ -7881,17 +8151,12 @@ class Connection {
7881
8151
 
7882
8152
 
7883
8153
  _wsOnLogsNotification(notification) {
7884
- const res = create(notification, LogsNotificationResult);
7885
- const keys = Object.keys(this._logsSubscriptions).map(Number);
7886
-
7887
- for (let id of keys) {
7888
- const sub = this._logsSubscriptions[id];
8154
+ const {
8155
+ result,
8156
+ subscription
8157
+ } = create(notification, LogsNotificationResult);
7889
8158
 
7890
- if (sub.subscriptionId === res.subscription) {
7891
- sub.callback(res.result.value, res.result.context);
7892
- return;
7893
- }
7894
- }
8159
+ this._handleServerNotification(subscription, [result.value, result.context]);
7895
8160
  }
7896
8161
  /**
7897
8162
  * @internal
@@ -7899,14 +8164,12 @@ class Connection {
7899
8164
 
7900
8165
 
7901
8166
  _wsOnSlotNotification(notification) {
7902
- const res = create(notification, SlotNotificationResult);
8167
+ const {
8168
+ result,
8169
+ subscription
8170
+ } = create(notification, SlotNotificationResult);
7903
8171
 
7904
- for (const sub of Object.values(this._slotSubscriptions)) {
7905
- if (sub.subscriptionId === res.subscription) {
7906
- sub.callback(res.result);
7907
- return;
7908
- }
7909
- }
8172
+ this._handleServerNotification(subscription, [result]);
7910
8173
  }
7911
8174
  /**
7912
8175
  * Register a callback to be invoked upon slot changes
@@ -7917,33 +8180,23 @@ class Connection {
7917
8180
 
7918
8181
 
7919
8182
  onSlotChange(callback) {
7920
- const id = ++this._slotSubscriptionCounter;
7921
- this._slotSubscriptions[id] = {
8183
+ return this._makeSubscription({
7922
8184
  callback,
7923
- subscriptionId: null
7924
- };
7925
-
7926
- this._updateSubscriptions();
7927
-
7928
- return id;
8185
+ method: 'slotSubscribe',
8186
+ unsubscribeMethod: 'slotUnsubscribe'
8187
+ }, []
8188
+ /* args */
8189
+ );
7929
8190
  }
7930
8191
  /**
7931
8192
  * Deregister a slot notification callback
7932
8193
  *
7933
- * @param id subscription id to deregister
8194
+ * @param id client subscription id to deregister
7934
8195
  */
7935
8196
 
7936
8197
 
7937
- async removeSlotChangeListener(id) {
7938
- if (this._slotSubscriptions[id]) {
7939
- const subInfo = this._slotSubscriptions[id];
7940
- delete this._slotSubscriptions[id];
7941
- await this._unsubscribe(subInfo, 'slotUnsubscribe');
7942
-
7943
- this._updateSubscriptions();
7944
- } else {
7945
- console.warn(createSubscriptionWarningMessage(id, 'slot change'));
7946
- }
8198
+ async removeSlotChangeListener(clientSubscriptionId) {
8199
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change');
7947
8200
  }
7948
8201
  /**
7949
8202
  * @internal
@@ -7951,14 +8204,12 @@ class Connection {
7951
8204
 
7952
8205
 
7953
8206
  _wsOnSlotUpdatesNotification(notification) {
7954
- const res = create(notification, SlotUpdateNotificationResult);
8207
+ const {
8208
+ result,
8209
+ subscription
8210
+ } = create(notification, SlotUpdateNotificationResult);
7955
8211
 
7956
- for (const sub of Object.values(this._slotUpdateSubscriptions)) {
7957
- if (sub.subscriptionId === res.subscription) {
7958
- sub.callback(res.result);
7959
- return;
7960
- }
7961
- }
8212
+ this._handleServerNotification(subscription, [result]);
7962
8213
  }
7963
8214
  /**
7964
8215
  * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
@@ -7970,32 +8221,36 @@ class Connection {
7970
8221
 
7971
8222
 
7972
8223
  onSlotUpdate(callback) {
7973
- const id = ++this._slotUpdateSubscriptionCounter;
7974
- this._slotUpdateSubscriptions[id] = {
8224
+ return this._makeSubscription({
7975
8225
  callback,
7976
- subscriptionId: null
7977
- };
7978
-
7979
- this._updateSubscriptions();
7980
-
7981
- return id;
8226
+ method: 'slotsUpdatesSubscribe',
8227
+ unsubscribeMethod: 'slotsUpdatesUnsubscribe'
8228
+ }, []
8229
+ /* args */
8230
+ );
7982
8231
  }
7983
8232
  /**
7984
8233
  * Deregister a slot update notification callback
7985
8234
  *
7986
- * @param id subscription id to deregister
8235
+ * @param id client subscription id to deregister
7987
8236
  */
7988
8237
 
7989
8238
 
7990
- async removeSlotUpdateListener(id) {
7991
- if (this._slotUpdateSubscriptions[id]) {
7992
- const subInfo = this._slotUpdateSubscriptions[id];
7993
- delete this._slotUpdateSubscriptions[id];
7994
- await this._unsubscribe(subInfo, 'slotsUpdatesUnsubscribe');
8239
+ async removeSlotUpdateListener(clientSubscriptionId) {
8240
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update');
8241
+ }
8242
+ /**
8243
+ * @internal
8244
+ */
7995
8245
 
7996
- this._updateSubscriptions();
8246
+
8247
+ async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) {
8248
+ const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
8249
+
8250
+ if (dispose) {
8251
+ await dispose();
7997
8252
  } else {
7998
- console.warn(createSubscriptionWarningMessage(id, 'slot update'));
8253
+ console.warn('Ignored unsubscribe request because an active subscription with id ' + `\`${clientSubscriptionId}\` for '${subscriptionName}' events ` + 'could not be found.');
7999
8254
  }
8000
8255
  }
8001
8256
 
@@ -8042,30 +8297,34 @@ class Connection {
8042
8297
 
8043
8298
 
8044
8299
  _wsOnSignatureNotification(notification) {
8045
- const res = create(notification, SignatureNotificationResult);
8046
-
8047
- for (const [id, sub] of Object.entries(this._signatureSubscriptions)) {
8048
- if (sub.subscriptionId === res.subscription) {
8049
- if (res.result.value === 'receivedSignature') {
8050
- sub.callback({
8051
- type: 'received'
8052
- }, res.result.context);
8053
- } else {
8054
- // Signatures subscriptions are auto-removed by the RPC service so
8055
- // no need to explicitly send an unsubscribe message
8056
- delete this._signatureSubscriptions[Number(id)];
8057
-
8058
- this._updateSubscriptions();
8059
-
8060
- sub.callback({
8061
- type: 'status',
8062
- result: res.result.value
8063
- }, res.result.context);
8064
- }
8065
-
8066
- return;
8067
- }
8068
- }
8300
+ const {
8301
+ result,
8302
+ subscription
8303
+ } = create(notification, SignatureNotificationResult);
8304
+
8305
+ if (result.value !== 'receivedSignature') {
8306
+ /**
8307
+ * Special case.
8308
+ * After a signature is processed, RPCs automatically dispose of the
8309
+ * subscription on the server side. We need to track which of these
8310
+ * subscriptions have been disposed in such a way, so that we know
8311
+ * whether the client is dealing with a not-yet-processed signature
8312
+ * (in which case we must tear down the server subscription) or an
8313
+ * already-processed signature (in which case the client can simply
8314
+ * clear out the subscription locally without telling the server).
8315
+ *
8316
+ * NOTE: There is a proposal to eliminate this special case, here:
8317
+ * https://github.com/solana-labs/solana/issues/18892
8318
+ */
8319
+ this._subscriptionsAutoDisposedByRpc.add(subscription);
8320
+ }
8321
+
8322
+ this._handleServerNotification(subscription, result.value === 'receivedSignature' ? [{
8323
+ type: 'received'
8324
+ }, result.context] : [{
8325
+ type: 'status',
8326
+ result: result.value
8327
+ }, result.context]);
8069
8328
  }
8070
8329
  /**
8071
8330
  * Register a callback to be invoked upon signature updates
@@ -8078,23 +8337,26 @@ class Connection {
8078
8337
 
8079
8338
 
8080
8339
  onSignature(signature, callback, commitment) {
8081
- const id = ++this._signatureSubscriptionCounter;
8082
- this._signatureSubscriptions[id] = {
8083
- signature,
8340
+ const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default.
8341
+ );
8342
+
8343
+ const clientSubscriptionId = this._makeSubscription({
8084
8344
  callback: (notification, context) => {
8085
8345
  if (notification.type === 'status') {
8086
- callback(notification.result, context);
8346
+ callback(notification.result, context); // Signatures subscriptions are auto-removed by the RPC service
8347
+ // so no need to explicitly send an unsubscribe message.
8348
+
8349
+ try {
8350
+ this.removeSignatureListener(clientSubscriptionId); // eslint-disable-next-line no-empty
8351
+ } catch {// Already removed.
8352
+ }
8087
8353
  }
8088
8354
  },
8089
- options: {
8090
- commitment
8091
- },
8092
- subscriptionId: null
8093
- };
8355
+ method: 'signatureSubscribe',
8356
+ unsubscribeMethod: 'signatureUnsubscribe'
8357
+ }, args);
8094
8358
 
8095
- this._updateSubscriptions();
8096
-
8097
- return id;
8359
+ return clientSubscriptionId;
8098
8360
  }
8099
8361
  /**
8100
8362
  * Register a callback to be invoked when a transaction is
@@ -8109,35 +8371,43 @@ class Connection {
8109
8371
 
8110
8372
 
8111
8373
  onSignatureWithOptions(signature, callback, options) {
8112
- const id = ++this._signatureSubscriptionCounter;
8113
- this._signatureSubscriptions[id] = {
8114
- signature,
8115
- callback,
8116
- options,
8117
- subscriptionId: null
8374
+ const {
8375
+ commitment,
8376
+ ...extra
8377
+ } = { ...options,
8378
+ commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default.
8379
+
8118
8380
  };
8119
8381
 
8120
- this._updateSubscriptions();
8382
+ const args = this._buildArgs([signature], commitment, undefined
8383
+ /* encoding */
8384
+ , extra);
8121
8385
 
8122
- return id;
8386
+ const clientSubscriptionId = this._makeSubscription({
8387
+ callback: (notification, context) => {
8388
+ callback(notification, context); // Signatures subscriptions are auto-removed by the RPC service
8389
+ // so no need to explicitly send an unsubscribe message.
8390
+
8391
+ try {
8392
+ this.removeSignatureListener(clientSubscriptionId); // eslint-disable-next-line no-empty
8393
+ } catch {// Already removed.
8394
+ }
8395
+ },
8396
+ method: 'signatureSubscribe',
8397
+ unsubscribeMethod: 'signatureUnsubscribe'
8398
+ }, args);
8399
+
8400
+ return clientSubscriptionId;
8123
8401
  }
8124
8402
  /**
8125
8403
  * Deregister a signature notification callback
8126
8404
  *
8127
- * @param id subscription id to deregister
8405
+ * @param id client subscription id to deregister
8128
8406
  */
8129
8407
 
8130
8408
 
8131
- async removeSignatureListener(id) {
8132
- if (this._signatureSubscriptions[id]) {
8133
- const subInfo = this._signatureSubscriptions[id];
8134
- delete this._signatureSubscriptions[id];
8135
- await this._unsubscribe(subInfo, 'signatureUnsubscribe');
8136
-
8137
- this._updateSubscriptions();
8138
- } else {
8139
- console.warn(createSubscriptionWarningMessage(id, 'signature result'));
8140
- }
8409
+ async removeSignatureListener(clientSubscriptionId) {
8410
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result');
8141
8411
  }
8142
8412
  /**
8143
8413
  * @internal
@@ -8145,14 +8415,12 @@ class Connection {
8145
8415
 
8146
8416
 
8147
8417
  _wsOnRootNotification(notification) {
8148
- const res = create(notification, RootNotificationResult);
8418
+ const {
8419
+ result,
8420
+ subscription
8421
+ } = create(notification, RootNotificationResult);
8149
8422
 
8150
- for (const sub of Object.values(this._rootSubscriptions)) {
8151
- if (sub.subscriptionId === res.subscription) {
8152
- sub.callback(res.result);
8153
- return;
8154
- }
8155
- }
8423
+ this._handleServerNotification(subscription, [result]);
8156
8424
  }
8157
8425
  /**
8158
8426
  * Register a callback to be invoked upon root changes
@@ -8163,33 +8431,23 @@ class Connection {
8163
8431
 
8164
8432
 
8165
8433
  onRootChange(callback) {
8166
- const id = ++this._rootSubscriptionCounter;
8167
- this._rootSubscriptions[id] = {
8434
+ return this._makeSubscription({
8168
8435
  callback,
8169
- subscriptionId: null
8170
- };
8171
-
8172
- this._updateSubscriptions();
8173
-
8174
- return id;
8436
+ method: 'rootSubscribe',
8437
+ unsubscribeMethod: 'rootUnsubscribe'
8438
+ }, []
8439
+ /* args */
8440
+ );
8175
8441
  }
8176
8442
  /**
8177
8443
  * Deregister a root notification callback
8178
8444
  *
8179
- * @param id subscription id to deregister
8445
+ * @param id client subscription id to deregister
8180
8446
  */
8181
8447
 
8182
8448
 
8183
- async removeRootChangeListener(id) {
8184
- if (this._rootSubscriptions[id]) {
8185
- const subInfo = this._rootSubscriptions[id];
8186
- delete this._rootSubscriptions[id];
8187
- await this._unsubscribe(subInfo, 'rootUnsubscribe');
8188
-
8189
- this._updateSubscriptions();
8190
- } else {
8191
- console.warn(createSubscriptionWarningMessage(id, 'root change'));
8192
- }
8449
+ async removeRootChangeListener(clientSubscriptionId) {
8450
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change');
8193
8451
  }
8194
8452
 
8195
8453
  }
@@ -9869,5 +10127,5 @@ function clusterApiUrl(cluster, tls) {
9869
10127
 
9870
10128
  const LAMPORTS_PER_SOL = 1000000000;
9871
10129
 
9872
- export { Account, Authorized, BLOCKHASH_CACHE_TIMEOUT_MS, BPF_LOADER_DEPRECATED_PROGRAM_ID, BPF_LOADER_PROGRAM_ID, BpfLoader, Connection, Ed25519Program, Enum, EpochSchedule, FeeCalculatorLayout, Keypair, LAMPORTS_PER_SOL, Loader, Lockup, MAX_SEED_LENGTH, Message, NONCE_ACCOUNT_LENGTH, NonceAccount, PACKET_DATA_SIZE, PublicKey, SOLANA_SCHEMA, STAKE_CONFIG_ID, STAKE_INSTRUCTION_LAYOUTS, SYSTEM_INSTRUCTION_LAYOUTS, SYSVAR_CLOCK_PUBKEY, SYSVAR_EPOCH_SCHEDULE_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY, SYSVAR_REWARDS_PUBKEY, SYSVAR_SLOT_HASHES_PUBKEY, SYSVAR_SLOT_HISTORY_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY, Secp256k1Program, SendTransactionError, StakeAuthorizationLayout, StakeInstruction, StakeProgram, Struct, SystemInstruction, SystemProgram, Transaction, TransactionInstruction, VALIDATOR_INFO_KEY, VOTE_PROGRAM_ID, ValidatorInfo, VoteAccount, VoteAuthorizationLayout, VoteInit, VoteInstruction, VoteProgram, clusterApiUrl, sendAndConfirmRawTransaction, sendAndConfirmTransaction };
10130
+ export { Account, Authorized, BLOCKHASH_CACHE_TIMEOUT_MS, BPF_LOADER_DEPRECATED_PROGRAM_ID, BPF_LOADER_PROGRAM_ID, BpfLoader, COMPUTE_BUDGET_INSTRUCTION_LAYOUTS, ComputeBudgetInstruction, ComputeBudgetProgram, Connection, Ed25519Program, Enum, EpochSchedule, FeeCalculatorLayout, Keypair, LAMPORTS_PER_SOL, Loader, Lockup, MAX_SEED_LENGTH, Message, NONCE_ACCOUNT_LENGTH, NonceAccount, PublicKey, SOLANA_SCHEMA, STAKE_CONFIG_ID, STAKE_INSTRUCTION_LAYOUTS, SYSTEM_INSTRUCTION_LAYOUTS, SYSVAR_CLOCK_PUBKEY, SYSVAR_EPOCH_SCHEDULE_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY, SYSVAR_REWARDS_PUBKEY, SYSVAR_SLOT_HASHES_PUBKEY, SYSVAR_SLOT_HISTORY_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY, Secp256k1Program, SendTransactionError, StakeAuthorizationLayout, StakeInstruction, StakeProgram, Struct, SystemInstruction, SystemProgram, Transaction, TransactionInstruction, VALIDATOR_INFO_KEY, VOTE_PROGRAM_ID, ValidatorInfo, VoteAccount, VoteAuthorizationLayout, VoteInit, VoteInstruction, VoteProgram, clusterApiUrl, sendAndConfirmRawTransaction, sendAndConfirmTransaction };
9873
10131
  //# sourceMappingURL=index.browser.esm.js.map