@solana/web3.js 1.41.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.
@@ -4761,6 +4761,82 @@ module.exports = exports;
4761
4761
 
4762
4762
  var crossFetch = /*@__PURE__*/getDefaultExportFromCjs(browserPonyfill.exports);
4763
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
+
4764
4840
  const MINIMUM_SLOT_PER_EPOCH = 32; // Returns the number of trailing zeros in the binary representation of self.
4765
4841
 
4766
4842
  function trailingZeros(n) {
@@ -4927,6 +5003,12 @@ const BufferFromRawAccountData = coerce(instance(Buffer), RawAccountDataResult,
4927
5003
  */
4928
5004
 
4929
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
+ */
4930
5012
 
4931
5013
  /**
4932
5014
  * @internal
@@ -5795,14 +5877,9 @@ const LogsNotificationResult = type({
5795
5877
  * Filter for log subscriptions.
5796
5878
  */
5797
5879
 
5798
- function createSubscriptionWarningMessage(id, label) {
5799
- return 'Ignored unsubscribe request because an active subscription ' + `with id \`${id}\` for '${label}' events could not be found.`;
5800
- }
5801
5880
  /**
5802
5881
  * A connection to a fullnode JSON RPC endpoint
5803
5882
  */
5804
-
5805
-
5806
5883
  class Connection {
5807
5884
  /** @internal */
5808
5885
 
@@ -5826,21 +5903,13 @@ class Connection {
5826
5903
 
5827
5904
  /** @internal */
5828
5905
 
5829
- /** @internal */
5830
-
5831
- /** @internal */
5832
-
5833
- /** @internal */
5834
-
5835
- /** @internal */
5836
-
5837
- /** @internal */
5838
-
5839
- /** @internal */
5840
-
5841
- /** @internal */
5842
-
5843
- /** @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
+ */
5844
5913
 
5845
5914
  /** @internal */
5846
5915
 
@@ -5856,7 +5925,19 @@ class Connection {
5856
5925
 
5857
5926
  /** @internal */
5858
5927
 
5859
- /** @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
+ */
5860
5941
 
5861
5942
  /** @internal */
5862
5943
 
@@ -5878,6 +5959,7 @@ class Connection {
5878
5959
  this._rpcWebSocketConnected = false;
5879
5960
  this._rpcWebSocketHeartbeat = null;
5880
5961
  this._rpcWebSocketIdleTimeout = null;
5962
+ this._rpcWebSocketGeneration = 0;
5881
5963
  this._disableBlockhashCaching = false;
5882
5964
  this._pollingBlockhash = false;
5883
5965
  this._blockhashInfo = {
@@ -5886,20 +5968,11 @@ class Connection {
5886
5968
  transactionSignatures: [],
5887
5969
  simulatedSignatures: []
5888
5970
  };
5889
- this._accountChangeSubscriptionCounter = 0;
5890
- this._accountChangeSubscriptions = {};
5891
- this._programAccountChangeSubscriptionCounter = 0;
5892
- this._programAccountChangeSubscriptions = {};
5893
- this._rootSubscriptionCounter = 0;
5894
- this._rootSubscriptions = {};
5895
- this._signatureSubscriptionCounter = 0;
5896
- this._signatureSubscriptions = {};
5897
- this._slotSubscriptionCounter = 0;
5898
- this._slotSubscriptions = {};
5899
- this._logsSubscriptionCounter = 0;
5900
- this._logsSubscriptions = {};
5901
- this._slotUpdateSubscriptionCounter = 0;
5902
- this._slotUpdateSubscriptions = {};
5971
+ this._nextClientSubscriptionId = 0;
5972
+ this._subscriptionDisposeFunctionsByClientSubscriptionId = {};
5973
+ this._subscriptionCallbacksByServerSubscriptionId = {};
5974
+ this._subscriptionsByHash = {};
5975
+ this._subscriptionsAutoDisposedByRpc = new Set();
5903
5976
  let url = new URL(endpoint);
5904
5977
  const useHttps = url.protocol === 'https:';
5905
5978
  let wsEndpoint;
@@ -7667,6 +7740,8 @@ class Connection {
7667
7740
 
7668
7741
 
7669
7742
  _wsOnClose(code) {
7743
+ this._rpcWebSocketGeneration++;
7744
+
7670
7745
  if (this._rpcWebSocketHeartbeat) {
7671
7746
  clearInterval(this._rpcWebSocketHeartbeat);
7672
7747
  this._rpcWebSocketHeartbeat = null;
@@ -7680,85 +7755,20 @@ class Connection {
7680
7755
  } // implicit close, prepare subscriptions for auto-reconnect
7681
7756
 
7682
7757
 
7683
- this._resetSubscriptions();
7684
- }
7685
- /**
7686
- * @internal
7687
- */
7688
-
7689
-
7690
- async _subscribe(sub, rpcMethod, rpcArgs) {
7691
- if (sub.subscriptionId == null) {
7692
- sub.subscriptionId = 'subscribing';
7693
-
7694
- try {
7695
- const id = await this._rpcWebSocket.call(rpcMethod, rpcArgs);
7696
-
7697
- if (typeof id === 'number' && sub.subscriptionId === 'subscribing') {
7698
- // eslint-disable-next-line require-atomic-updates
7699
- sub.subscriptionId = id;
7700
- }
7701
- } catch (err) {
7702
- if (sub.subscriptionId === 'subscribing') {
7703
- // eslint-disable-next-line require-atomic-updates
7704
- sub.subscriptionId = null;
7705
- }
7706
-
7707
- if (err instanceof Error) {
7708
- console.error(`${rpcMethod} error for argument`, rpcArgs, err.message);
7709
- }
7710
- }
7711
- }
7712
- }
7713
- /**
7714
- * @internal
7715
- */
7716
-
7717
-
7718
- async _unsubscribe(sub, rpcMethod) {
7719
- const subscriptionId = sub.subscriptionId;
7720
-
7721
- if (subscriptionId != null && typeof subscriptionId != 'string') {
7722
- const unsubscribeId = subscriptionId;
7723
-
7724
- try {
7725
- await this._rpcWebSocket.call(rpcMethod, [unsubscribeId]);
7726
- } catch (err) {
7727
- if (err instanceof Error) {
7728
- console.error(`${rpcMethod} error:`, err.message);
7729
- }
7730
- }
7731
- }
7732
- }
7733
- /**
7734
- * @internal
7735
- */
7736
-
7737
-
7738
- _resetSubscriptions() {
7739
- Object.values(this._accountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7740
- Object.values(this._logsSubscriptions).forEach(s => s.subscriptionId = null);
7741
- Object.values(this._programAccountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7742
- Object.values(this._rootSubscriptions).forEach(s => s.subscriptionId = null);
7743
- Object.values(this._signatureSubscriptions).forEach(s => s.subscriptionId = null);
7744
- Object.values(this._slotSubscriptions).forEach(s => s.subscriptionId = null);
7745
- 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
+ });
7746
7764
  }
7747
7765
  /**
7748
7766
  * @internal
7749
7767
  */
7750
7768
 
7751
7769
 
7752
- _updateSubscriptions() {
7753
- const accountKeys = Object.keys(this._accountChangeSubscriptions).map(Number);
7754
- const programKeys = Object.keys(this._programAccountChangeSubscriptions).map(Number);
7755
- const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
7756
- const slotUpdateKeys = Object.keys(this._slotUpdateSubscriptions).map(Number);
7757
- const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
7758
- const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
7759
- const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
7760
-
7761
- 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) {
7762
7772
  if (this._rpcWebSocketConnected) {
7763
7773
  this._rpcWebSocketConnected = false;
7764
7774
  this._rpcWebSocketIdleTimeout = setTimeout(() => {
@@ -7790,60 +7800,167 @@ class Connection {
7790
7800
  return;
7791
7801
  }
7792
7802
 
7793
- for (let id of accountKeys) {
7794
- const sub = this._accountChangeSubscriptions[id];
7795
-
7796
- this._subscribe(sub, 'accountSubscribe', this._buildArgs([sub.publicKey], sub.commitment, 'base64'));
7797
- }
7803
+ const activeWebSocketGeneration = this._rpcWebSocketGeneration;
7798
7804
 
7799
- for (let id of programKeys) {
7800
- const sub = this._programAccountChangeSubscriptions[id];
7805
+ const isCurrentConnectionStillActive = () => {
7806
+ return activeWebSocketGeneration === this._rpcWebSocketGeneration;
7807
+ };
7801
7808
 
7802
- this._subscribe(sub, 'programSubscribe', this._buildArgs([sub.programId], sub.commitment, 'base64', {
7803
- filters: sub.filters
7804
- }));
7805
- }
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];
7806
7815
 
7807
- for (let id of slotKeys) {
7808
- const sub = this._slotSubscriptions[id];
7816
+ if (subscription === undefined) {
7817
+ // This entry has since been deleted. Skip.
7818
+ return;
7819
+ }
7809
7820
 
7810
- this._subscribe(sub, 'slotSubscribe', []);
7811
- }
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
+ }
7812
7843
 
7813
- for (let id of slotUpdateKeys) {
7814
- const sub = this._slotUpdateSubscriptions[id];
7844
+ await this._updateSubscriptions();
7845
+ return;
7846
+ }
7815
7847
 
7816
- this._subscribe(sub, 'slotsUpdatesSubscribe', []);
7817
- }
7848
+ await (async () => {
7849
+ const {
7850
+ args,
7851
+ method
7852
+ } = subscription;
7818
7853
 
7819
- for (let id of signatureKeys) {
7820
- const sub = this._signatureSubscriptions[id];
7821
- const args = [sub.signature];
7822
- 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?
7823
7873
 
7824
- this._subscribe(sub, 'signatureSubscribe', args);
7825
- }
7826
7874
 
7827
- for (let id of rootKeys) {
7828
- const sub = this._rootSubscriptions[id];
7875
+ this._subscriptionsByHash[hash] = { ...subscription,
7876
+ state: 'pending'
7877
+ };
7878
+ await this._updateSubscriptions();
7879
+ }
7880
+ })();
7881
+ break;
7829
7882
 
7830
- this._subscribe(sub, 'rootSubscribe', []);
7831
- }
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
+ }
7832
7929
 
7833
- for (let id of logsKeys) {
7834
- const sub = this._logsSubscriptions[id];
7835
- let filter;
7930
+ this._subscriptionsByHash[hash] = { ...subscription,
7931
+ state: 'unsubscribed'
7932
+ };
7933
+ await this._updateSubscriptions();
7934
+ })();
7935
+ }
7836
7936
 
7837
- if (typeof sub.filter === 'object') {
7838
- filter = {
7839
- mentions: [sub.filter.toString()]
7840
- };
7841
- } else {
7842
- filter = sub.filter;
7937
+ break;
7843
7938
  }
7939
+ }));
7940
+ }
7941
+ /**
7942
+ * @internal
7943
+ */
7844
7944
 
7845
- this._subscribe(sub, 'logsSubscribe', this._buildArgs([filter], sub.commitment));
7945
+
7946
+ _handleServerNotification(serverSubscriptionId, callbackArgs) {
7947
+ const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
7948
+
7949
+ if (callbacks === undefined) {
7950
+ return;
7846
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
+ });
7847
7964
  }
7848
7965
  /**
7849
7966
  * @internal
@@ -7851,14 +7968,71 @@ class Connection {
7851
7968
 
7852
7969
 
7853
7970
  _wsOnAccountNotification(notification) {
7854
- const res = create(notification, AccountNotificationResult);
7971
+ const {
7972
+ result,
7973
+ subscription
7974
+ } = create(notification, AccountNotificationResult);
7855
7975
 
7856
- for (const sub of Object.values(this._accountChangeSubscriptions)) {
7857
- if (sub.subscriptionId === res.subscription) {
7858
- sub.callback(res.result.value, res.result.context);
7859
- return;
7860
- }
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);
7861
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;
7862
8036
  }
7863
8037
  /**
7864
8038
  * Register a callback to be invoked whenever the specified account changes
@@ -7871,35 +8045,24 @@ class Connection {
7871
8045
 
7872
8046
 
7873
8047
  onAccountChange(publicKey, callback, commitment) {
7874
- const id = ++this._accountChangeSubscriptionCounter;
7875
- this._accountChangeSubscriptions[id] = {
7876
- publicKey: publicKey.toBase58(),
7877
- callback,
7878
- commitment,
7879
- subscriptionId: null
7880
- };
8048
+ const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
8049
+ 'base64');
7881
8050
 
7882
- this._updateSubscriptions();
7883
-
7884
- return id;
8051
+ return this._makeSubscription({
8052
+ callback,
8053
+ method: 'accountSubscribe',
8054
+ unsubscribeMethod: 'accountUnsubscribe'
8055
+ }, args);
7885
8056
  }
7886
8057
  /**
7887
8058
  * Deregister an account notification callback
7888
8059
  *
7889
- * @param id subscription id to deregister
8060
+ * @param id client subscription id to deregister
7890
8061
  */
7891
8062
 
7892
8063
 
7893
- async removeAccountChangeListener(id) {
7894
- if (this._accountChangeSubscriptions[id]) {
7895
- const subInfo = this._accountChangeSubscriptions[id];
7896
- delete this._accountChangeSubscriptions[id];
7897
- await this._unsubscribe(subInfo, 'accountUnsubscribe');
7898
-
7899
- this._updateSubscriptions();
7900
- } else {
7901
- console.warn(createSubscriptionWarningMessage(id, 'account change'));
7902
- }
8064
+ async removeAccountChangeListener(clientSubscriptionId) {
8065
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change');
7903
8066
  }
7904
8067
  /**
7905
8068
  * @internal
@@ -7907,21 +8070,15 @@ class Connection {
7907
8070
 
7908
8071
 
7909
8072
  _wsOnProgramAccountNotification(notification) {
7910
- const res = create(notification, ProgramAccountNotificationResult);
8073
+ const {
8074
+ result,
8075
+ subscription
8076
+ } = create(notification, ProgramAccountNotificationResult);
7911
8077
 
7912
- for (const sub of Object.values(this._programAccountChangeSubscriptions)) {
7913
- if (sub.subscriptionId === res.subscription) {
7914
- const {
7915
- value,
7916
- context
7917
- } = res.result;
7918
- sub.callback({
7919
- accountId: value.pubkey,
7920
- accountInfo: value.account
7921
- }, context);
7922
- return;
7923
- }
7924
- }
8078
+ this._handleServerNotification(subscription, [{
8079
+ accountId: result.value.pubkey,
8080
+ accountInfo: result.value.account
8081
+ }, result.context]);
7925
8082
  }
7926
8083
  /**
7927
8084
  * Register a callback to be invoked whenever accounts owned by the
@@ -7936,36 +8093,30 @@ class Connection {
7936
8093
 
7937
8094
 
7938
8095
  onProgramAccountChange(programId, callback, commitment, filters) {
7939
- const id = ++this._programAccountChangeSubscriptionCounter;
7940
- this._programAccountChangeSubscriptions[id] = {
7941
- 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({
7942
8106
  callback,
7943
- commitment,
7944
- subscriptionId: null,
7945
- filters
7946
- };
7947
-
7948
- this._updateSubscriptions();
7949
-
7950
- return id;
8107
+ method: 'programSubscribe',
8108
+ unsubscribeMethod: 'programUnsubscribe'
8109
+ }, args);
7951
8110
  }
7952
8111
  /**
7953
8112
  * Deregister an account notification callback
7954
8113
  *
7955
- * @param id subscription id to deregister
8114
+ * @param id client subscription id to deregister
7956
8115
  */
7957
8116
 
7958
8117
 
7959
- async removeProgramAccountChangeListener(id) {
7960
- if (this._programAccountChangeSubscriptions[id]) {
7961
- const subInfo = this._programAccountChangeSubscriptions[id];
7962
- delete this._programAccountChangeSubscriptions[id];
7963
- await this._unsubscribe(subInfo, 'programUnsubscribe');
7964
-
7965
- this._updateSubscriptions();
7966
- } else {
7967
- console.warn(createSubscriptionWarningMessage(id, 'program account change'));
7968
- }
8118
+ async removeProgramAccountChangeListener(clientSubscriptionId) {
8119
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change');
7969
8120
  }
7970
8121
  /**
7971
8122
  * Registers a callback to be invoked whenever logs are emitted.
@@ -7973,35 +8124,26 @@ class Connection {
7973
8124
 
7974
8125
 
7975
8126
  onLogs(filter, callback, commitment) {
7976
- const id = ++this._logsSubscriptionCounter;
7977
- this._logsSubscriptions[id] = {
7978
- filter,
7979
- callback,
7980
- commitment,
7981
- subscriptionId: null
7982
- };
7983
-
7984
- this._updateSubscriptions();
8127
+ const args = this._buildArgs([typeof filter === 'object' ? {
8128
+ mentions: [filter.toString()]
8129
+ } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default.
8130
+ );
7985
8131
 
7986
- return id;
8132
+ return this._makeSubscription({
8133
+ callback,
8134
+ method: 'logsSubscribe',
8135
+ unsubscribeMethod: 'logsUnsubscribe'
8136
+ }, args);
7987
8137
  }
7988
8138
  /**
7989
8139
  * Deregister a logs callback.
7990
8140
  *
7991
- * @param id subscription id to deregister.
8141
+ * @param id client subscription id to deregister.
7992
8142
  */
7993
8143
 
7994
8144
 
7995
- async removeOnLogsListener(id) {
7996
- if (this._logsSubscriptions[id]) {
7997
- const subInfo = this._logsSubscriptions[id];
7998
- delete this._logsSubscriptions[id];
7999
- await this._unsubscribe(subInfo, 'logsUnsubscribe');
8000
-
8001
- this._updateSubscriptions();
8002
- } else {
8003
- console.warn(createSubscriptionWarningMessage(id, 'logs'));
8004
- }
8145
+ async removeOnLogsListener(clientSubscriptionId) {
8146
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs');
8005
8147
  }
8006
8148
  /**
8007
8149
  * @internal
@@ -8009,17 +8151,12 @@ class Connection {
8009
8151
 
8010
8152
 
8011
8153
  _wsOnLogsNotification(notification) {
8012
- const res = create(notification, LogsNotificationResult);
8013
- const keys = Object.keys(this._logsSubscriptions).map(Number);
8014
-
8015
- for (let id of keys) {
8016
- const sub = this._logsSubscriptions[id];
8154
+ const {
8155
+ result,
8156
+ subscription
8157
+ } = create(notification, LogsNotificationResult);
8017
8158
 
8018
- if (sub.subscriptionId === res.subscription) {
8019
- sub.callback(res.result.value, res.result.context);
8020
- return;
8021
- }
8022
- }
8159
+ this._handleServerNotification(subscription, [result.value, result.context]);
8023
8160
  }
8024
8161
  /**
8025
8162
  * @internal
@@ -8027,14 +8164,12 @@ class Connection {
8027
8164
 
8028
8165
 
8029
8166
  _wsOnSlotNotification(notification) {
8030
- const res = create(notification, SlotNotificationResult);
8167
+ const {
8168
+ result,
8169
+ subscription
8170
+ } = create(notification, SlotNotificationResult);
8031
8171
 
8032
- for (const sub of Object.values(this._slotSubscriptions)) {
8033
- if (sub.subscriptionId === res.subscription) {
8034
- sub.callback(res.result);
8035
- return;
8036
- }
8037
- }
8172
+ this._handleServerNotification(subscription, [result]);
8038
8173
  }
8039
8174
  /**
8040
8175
  * Register a callback to be invoked upon slot changes
@@ -8045,33 +8180,23 @@ class Connection {
8045
8180
 
8046
8181
 
8047
8182
  onSlotChange(callback) {
8048
- const id = ++this._slotSubscriptionCounter;
8049
- this._slotSubscriptions[id] = {
8183
+ return this._makeSubscription({
8050
8184
  callback,
8051
- subscriptionId: null
8052
- };
8053
-
8054
- this._updateSubscriptions();
8055
-
8056
- return id;
8185
+ method: 'slotSubscribe',
8186
+ unsubscribeMethod: 'slotUnsubscribe'
8187
+ }, []
8188
+ /* args */
8189
+ );
8057
8190
  }
8058
8191
  /**
8059
8192
  * Deregister a slot notification callback
8060
8193
  *
8061
- * @param id subscription id to deregister
8194
+ * @param id client subscription id to deregister
8062
8195
  */
8063
8196
 
8064
8197
 
8065
- async removeSlotChangeListener(id) {
8066
- if (this._slotSubscriptions[id]) {
8067
- const subInfo = this._slotSubscriptions[id];
8068
- delete this._slotSubscriptions[id];
8069
- await this._unsubscribe(subInfo, 'slotUnsubscribe');
8070
-
8071
- this._updateSubscriptions();
8072
- } else {
8073
- console.warn(createSubscriptionWarningMessage(id, 'slot change'));
8074
- }
8198
+ async removeSlotChangeListener(clientSubscriptionId) {
8199
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change');
8075
8200
  }
8076
8201
  /**
8077
8202
  * @internal
@@ -8079,14 +8204,12 @@ class Connection {
8079
8204
 
8080
8205
 
8081
8206
  _wsOnSlotUpdatesNotification(notification) {
8082
- const res = create(notification, SlotUpdateNotificationResult);
8207
+ const {
8208
+ result,
8209
+ subscription
8210
+ } = create(notification, SlotUpdateNotificationResult);
8083
8211
 
8084
- for (const sub of Object.values(this._slotUpdateSubscriptions)) {
8085
- if (sub.subscriptionId === res.subscription) {
8086
- sub.callback(res.result);
8087
- return;
8088
- }
8089
- }
8212
+ this._handleServerNotification(subscription, [result]);
8090
8213
  }
8091
8214
  /**
8092
8215
  * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
@@ -8098,32 +8221,36 @@ class Connection {
8098
8221
 
8099
8222
 
8100
8223
  onSlotUpdate(callback) {
8101
- const id = ++this._slotUpdateSubscriptionCounter;
8102
- this._slotUpdateSubscriptions[id] = {
8224
+ return this._makeSubscription({
8103
8225
  callback,
8104
- subscriptionId: null
8105
- };
8106
-
8107
- this._updateSubscriptions();
8108
-
8109
- return id;
8226
+ method: 'slotsUpdatesSubscribe',
8227
+ unsubscribeMethod: 'slotsUpdatesUnsubscribe'
8228
+ }, []
8229
+ /* args */
8230
+ );
8110
8231
  }
8111
8232
  /**
8112
8233
  * Deregister a slot update notification callback
8113
8234
  *
8114
- * @param id subscription id to deregister
8235
+ * @param id client subscription id to deregister
8115
8236
  */
8116
8237
 
8117
8238
 
8118
- async removeSlotUpdateListener(id) {
8119
- if (this._slotUpdateSubscriptions[id]) {
8120
- const subInfo = this._slotUpdateSubscriptions[id];
8121
- delete this._slotUpdateSubscriptions[id];
8122
- await this._unsubscribe(subInfo, 'slotsUpdatesUnsubscribe');
8239
+ async removeSlotUpdateListener(clientSubscriptionId) {
8240
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update');
8241
+ }
8242
+ /**
8243
+ * @internal
8244
+ */
8123
8245
 
8124
- this._updateSubscriptions();
8246
+
8247
+ async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) {
8248
+ const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
8249
+
8250
+ if (dispose) {
8251
+ await dispose();
8125
8252
  } else {
8126
- 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.');
8127
8254
  }
8128
8255
  }
8129
8256
 
@@ -8170,30 +8297,34 @@ class Connection {
8170
8297
 
8171
8298
 
8172
8299
  _wsOnSignatureNotification(notification) {
8173
- const res = create(notification, SignatureNotificationResult);
8174
-
8175
- for (const [id, sub] of Object.entries(this._signatureSubscriptions)) {
8176
- if (sub.subscriptionId === res.subscription) {
8177
- if (res.result.value === 'receivedSignature') {
8178
- sub.callback({
8179
- type: 'received'
8180
- }, res.result.context);
8181
- } else {
8182
- // Signatures subscriptions are auto-removed by the RPC service so
8183
- // no need to explicitly send an unsubscribe message
8184
- delete this._signatureSubscriptions[Number(id)];
8185
-
8186
- this._updateSubscriptions();
8187
-
8188
- sub.callback({
8189
- type: 'status',
8190
- result: res.result.value
8191
- }, res.result.context);
8192
- }
8193
-
8194
- return;
8195
- }
8196
- }
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]);
8197
8328
  }
8198
8329
  /**
8199
8330
  * Register a callback to be invoked upon signature updates
@@ -8206,23 +8337,26 @@ class Connection {
8206
8337
 
8207
8338
 
8208
8339
  onSignature(signature, callback, commitment) {
8209
- const id = ++this._signatureSubscriptionCounter;
8210
- this._signatureSubscriptions[id] = {
8211
- signature,
8340
+ const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default.
8341
+ );
8342
+
8343
+ const clientSubscriptionId = this._makeSubscription({
8212
8344
  callback: (notification, context) => {
8213
8345
  if (notification.type === 'status') {
8214
- 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
+ }
8215
8353
  }
8216
8354
  },
8217
- options: {
8218
- commitment
8219
- },
8220
- subscriptionId: null
8221
- };
8355
+ method: 'signatureSubscribe',
8356
+ unsubscribeMethod: 'signatureUnsubscribe'
8357
+ }, args);
8222
8358
 
8223
- this._updateSubscriptions();
8224
-
8225
- return id;
8359
+ return clientSubscriptionId;
8226
8360
  }
8227
8361
  /**
8228
8362
  * Register a callback to be invoked when a transaction is
@@ -8237,35 +8371,43 @@ class Connection {
8237
8371
 
8238
8372
 
8239
8373
  onSignatureWithOptions(signature, callback, options) {
8240
- const id = ++this._signatureSubscriptionCounter;
8241
- this._signatureSubscriptions[id] = {
8242
- signature,
8243
- callback,
8244
- options,
8245
- subscriptionId: null
8374
+ const {
8375
+ commitment,
8376
+ ...extra
8377
+ } = { ...options,
8378
+ commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default.
8379
+
8246
8380
  };
8247
8381
 
8248
- this._updateSubscriptions();
8382
+ const args = this._buildArgs([signature], commitment, undefined
8383
+ /* encoding */
8384
+ , extra);
8249
8385
 
8250
- 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;
8251
8401
  }
8252
8402
  /**
8253
8403
  * Deregister a signature notification callback
8254
8404
  *
8255
- * @param id subscription id to deregister
8405
+ * @param id client subscription id to deregister
8256
8406
  */
8257
8407
 
8258
8408
 
8259
- async removeSignatureListener(id) {
8260
- if (this._signatureSubscriptions[id]) {
8261
- const subInfo = this._signatureSubscriptions[id];
8262
- delete this._signatureSubscriptions[id];
8263
- await this._unsubscribe(subInfo, 'signatureUnsubscribe');
8264
-
8265
- this._updateSubscriptions();
8266
- } else {
8267
- console.warn(createSubscriptionWarningMessage(id, 'signature result'));
8268
- }
8409
+ async removeSignatureListener(clientSubscriptionId) {
8410
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result');
8269
8411
  }
8270
8412
  /**
8271
8413
  * @internal
@@ -8273,14 +8415,12 @@ class Connection {
8273
8415
 
8274
8416
 
8275
8417
  _wsOnRootNotification(notification) {
8276
- const res = create(notification, RootNotificationResult);
8418
+ const {
8419
+ result,
8420
+ subscription
8421
+ } = create(notification, RootNotificationResult);
8277
8422
 
8278
- for (const sub of Object.values(this._rootSubscriptions)) {
8279
- if (sub.subscriptionId === res.subscription) {
8280
- sub.callback(res.result);
8281
- return;
8282
- }
8283
- }
8423
+ this._handleServerNotification(subscription, [result]);
8284
8424
  }
8285
8425
  /**
8286
8426
  * Register a callback to be invoked upon root changes
@@ -8291,33 +8431,23 @@ class Connection {
8291
8431
 
8292
8432
 
8293
8433
  onRootChange(callback) {
8294
- const id = ++this._rootSubscriptionCounter;
8295
- this._rootSubscriptions[id] = {
8434
+ return this._makeSubscription({
8296
8435
  callback,
8297
- subscriptionId: null
8298
- };
8299
-
8300
- this._updateSubscriptions();
8301
-
8302
- return id;
8436
+ method: 'rootSubscribe',
8437
+ unsubscribeMethod: 'rootUnsubscribe'
8438
+ }, []
8439
+ /* args */
8440
+ );
8303
8441
  }
8304
8442
  /**
8305
8443
  * Deregister a root notification callback
8306
8444
  *
8307
- * @param id subscription id to deregister
8445
+ * @param id client subscription id to deregister
8308
8446
  */
8309
8447
 
8310
8448
 
8311
- async removeRootChangeListener(id) {
8312
- if (this._rootSubscriptions[id]) {
8313
- const subInfo = this._rootSubscriptions[id];
8314
- delete this._rootSubscriptions[id];
8315
- await this._unsubscribe(subInfo, 'rootUnsubscribe');
8316
-
8317
- this._updateSubscriptions();
8318
- } else {
8319
- console.warn(createSubscriptionWarningMessage(id, 'root change'));
8320
- }
8449
+ async removeRootChangeListener(clientSubscriptionId) {
8450
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change');
8321
8451
  }
8322
8452
 
8323
8453
  }