@solana/web3.js 1.41.0 → 1.41.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2108,6 +2108,16 @@ class Account {
2108
2108
 
2109
2109
  const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey('BPFLoader1111111111111111111111111111111111');
2110
2110
 
2111
+ /**
2112
+ * Maximum over-the-wire size of a Transaction
2113
+ *
2114
+ * 1280 is IPv6 minimum MTU
2115
+ * 40 bytes is the size of the IPv6 header
2116
+ * 8 bytes is the size of the fragment header
2117
+ */
2118
+ const PACKET_DATA_SIZE = 1280 - 40 - 8;
2119
+ const SIGNATURE_LENGTH_IN_BYTES = 64;
2120
+
2111
2121
  /**
2112
2122
  * Layout for a public key
2113
2123
  */
@@ -2367,20 +2377,8 @@ function assert (condition, message) {
2367
2377
 
2368
2378
  /**
2369
2379
  * Default (empty) signature
2370
- *
2371
- * Signatures are 64 bytes in length
2372
2380
  */
2373
- const DEFAULT_SIGNATURE = buffer.Buffer.alloc(64).fill(0);
2374
- /**
2375
- * Maximum over-the-wire size of a Transaction
2376
- *
2377
- * 1280 is IPv6 minimum MTU
2378
- * 40 bytes is the size of the IPv6 header
2379
- * 8 bytes is the size of the fragment header
2380
- */
2381
-
2382
- const PACKET_DATA_SIZE = 1280 - 40 - 8;
2383
- const SIGNATURE_LENGTH = 64;
2381
+ const DEFAULT_SIGNATURE = buffer.Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0);
2384
2382
  /**
2385
2383
  * Account metadata used to define instructions
2386
2384
  */
@@ -3010,8 +3008,8 @@ class Transaction {
3010
3008
  let signatures = [];
3011
3009
 
3012
3010
  for (let i = 0; i < signatureCount; i++) {
3013
- const signature = byteArray.slice(0, SIGNATURE_LENGTH);
3014
- byteArray = byteArray.slice(SIGNATURE_LENGTH);
3011
+ const signature = byteArray.slice(0, SIGNATURE_LENGTH_IN_BYTES);
3012
+ byteArray = byteArray.slice(SIGNATURE_LENGTH_IN_BYTES);
3015
3013
  signatures.push(bs58__default["default"].encode(buffer.Buffer.from(signature)));
3016
3014
  }
3017
3015
 
@@ -3900,11 +3898,11 @@ class SystemProgram {
3900
3898
  }
3901
3899
  SystemProgram.programId = new PublicKey('11111111111111111111111111111111');
3902
3900
 
3903
- // Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the
3904
3901
  // rest of the Transaction fields
3905
3902
  //
3906
3903
  // TODO: replace 300 with a proper constant for the size of the other
3907
3904
  // Transaction fields
3905
+
3908
3906
  const CHUNK_SIZE = PACKET_DATA_SIZE - 300;
3909
3907
  /**
3910
3908
  * Program loader interface
@@ -4795,6 +4793,82 @@ module.exports = exports;
4795
4793
 
4796
4794
  var crossFetch = /*@__PURE__*/getDefaultExportFromCjs(browserPonyfill.exports);
4797
4795
 
4796
+ var objToString = Object.prototype.toString;
4797
+ var objKeys = Object.keys || function(obj) {
4798
+ var keys = [];
4799
+ for (var name in obj) {
4800
+ keys.push(name);
4801
+ }
4802
+ return keys;
4803
+ };
4804
+
4805
+ function stringify(val, isArrayProp) {
4806
+ var i, max, str, keys, key, propVal, toStr;
4807
+ if (val === true) {
4808
+ return "true";
4809
+ }
4810
+ if (val === false) {
4811
+ return "false";
4812
+ }
4813
+ switch (typeof val) {
4814
+ case "object":
4815
+ if (val === null) {
4816
+ return null;
4817
+ } else if (val.toJSON && typeof val.toJSON === "function") {
4818
+ return stringify(val.toJSON(), isArrayProp);
4819
+ } else {
4820
+ toStr = objToString.call(val);
4821
+ if (toStr === "[object Array]") {
4822
+ str = '[';
4823
+ max = val.length - 1;
4824
+ for(i = 0; i < max; i++) {
4825
+ str += stringify(val[i], true) + ',';
4826
+ }
4827
+ if (max > -1) {
4828
+ str += stringify(val[i], true);
4829
+ }
4830
+ return str + ']';
4831
+ } else if (toStr === "[object Object]") {
4832
+ // only object is left
4833
+ keys = objKeys(val).sort();
4834
+ max = keys.length;
4835
+ str = "";
4836
+ i = 0;
4837
+ while (i < max) {
4838
+ key = keys[i];
4839
+ propVal = stringify(val[key], false);
4840
+ if (propVal !== undefined) {
4841
+ if (str) {
4842
+ str += ',';
4843
+ }
4844
+ str += JSON.stringify(key) + ':' + propVal;
4845
+ }
4846
+ i++;
4847
+ }
4848
+ return '{' + str + '}';
4849
+ } else {
4850
+ return JSON.stringify(val);
4851
+ }
4852
+ }
4853
+ case "function":
4854
+ case "undefined":
4855
+ return isArrayProp ? null : undefined;
4856
+ case "string":
4857
+ return JSON.stringify(val);
4858
+ default:
4859
+ return isFinite(val) ? val : null;
4860
+ }
4861
+ }
4862
+
4863
+ var fastStableStringify = function(val) {
4864
+ var returnVal = stringify(val, false);
4865
+ if (returnVal !== undefined) {
4866
+ return ''+ returnVal;
4867
+ }
4868
+ };
4869
+
4870
+ var fastStableStringify$1 = fastStableStringify;
4871
+
4798
4872
  const MINIMUM_SLOT_PER_EPOCH = 32; // Returns the number of trailing zeros in the binary representation of self.
4799
4873
 
4800
4874
  function trailingZeros(n) {
@@ -4961,6 +5035,12 @@ const BufferFromRawAccountData = superstruct.coerce(superstruct.instance(buffer.
4961
5035
  */
4962
5036
 
4963
5037
  const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
5038
+ /**
5039
+ * HACK.
5040
+ * Copied from rpc-websockets/dist/lib/client.
5041
+ * Otherwise, `yarn build` fails with:
5042
+ * https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d
5043
+ */
4964
5044
 
4965
5045
  /**
4966
5046
  * @internal
@@ -5829,14 +5909,9 @@ const LogsNotificationResult = superstruct.type({
5829
5909
  * Filter for log subscriptions.
5830
5910
  */
5831
5911
 
5832
- function createSubscriptionWarningMessage(id, label) {
5833
- return 'Ignored unsubscribe request because an active subscription ' + `with id \`${id}\` for '${label}' events could not be found.`;
5834
- }
5835
5912
  /**
5836
5913
  * A connection to a fullnode JSON RPC endpoint
5837
5914
  */
5838
-
5839
-
5840
5915
  class Connection {
5841
5916
  /** @internal */
5842
5917
 
@@ -5860,21 +5935,13 @@ class Connection {
5860
5935
 
5861
5936
  /** @internal */
5862
5937
 
5863
- /** @internal */
5864
-
5865
- /** @internal */
5866
-
5867
- /** @internal */
5868
-
5869
- /** @internal */
5870
-
5871
- /** @internal */
5872
-
5873
- /** @internal */
5874
-
5875
- /** @internal */
5876
-
5877
- /** @internal */
5938
+ /** @internal
5939
+ * A number that we increment every time an active connection closes.
5940
+ * Used to determine whether the same socket connection that was open
5941
+ * when an async operation started is the same one that's active when
5942
+ * its continuation fires.
5943
+ *
5944
+ */
5878
5945
 
5879
5946
  /** @internal */
5880
5947
 
@@ -5890,7 +5957,19 @@ class Connection {
5890
5957
 
5891
5958
  /** @internal */
5892
5959
 
5893
- /** @internal */
5960
+ /**
5961
+ * Special case.
5962
+ * After a signature is processed, RPCs automatically dispose of the
5963
+ * subscription on the server side. We need to track which of these
5964
+ * subscriptions have been disposed in such a way, so that we know
5965
+ * whether the client is dealing with a not-yet-processed signature
5966
+ * (in which case we must tear down the server subscription) or an
5967
+ * already-processed signature (in which case the client can simply
5968
+ * clear out the subscription locally without telling the server).
5969
+ *
5970
+ * NOTE: There is a proposal to eliminate this special case, here:
5971
+ * https://github.com/solana-labs/solana/issues/18892
5972
+ */
5894
5973
 
5895
5974
  /** @internal */
5896
5975
 
@@ -5912,6 +5991,7 @@ class Connection {
5912
5991
  this._rpcWebSocketConnected = false;
5913
5992
  this._rpcWebSocketHeartbeat = null;
5914
5993
  this._rpcWebSocketIdleTimeout = null;
5994
+ this._rpcWebSocketGeneration = 0;
5915
5995
  this._disableBlockhashCaching = false;
5916
5996
  this._pollingBlockhash = false;
5917
5997
  this._blockhashInfo = {
@@ -5920,20 +6000,11 @@ class Connection {
5920
6000
  transactionSignatures: [],
5921
6001
  simulatedSignatures: []
5922
6002
  };
5923
- this._accountChangeSubscriptionCounter = 0;
5924
- this._accountChangeSubscriptions = {};
5925
- this._programAccountChangeSubscriptionCounter = 0;
5926
- this._programAccountChangeSubscriptions = {};
5927
- this._rootSubscriptionCounter = 0;
5928
- this._rootSubscriptions = {};
5929
- this._signatureSubscriptionCounter = 0;
5930
- this._signatureSubscriptions = {};
5931
- this._slotSubscriptionCounter = 0;
5932
- this._slotSubscriptions = {};
5933
- this._logsSubscriptionCounter = 0;
5934
- this._logsSubscriptions = {};
5935
- this._slotUpdateSubscriptionCounter = 0;
5936
- this._slotUpdateSubscriptions = {};
6003
+ this._nextClientSubscriptionId = 0;
6004
+ this._subscriptionDisposeFunctionsByClientSubscriptionId = {};
6005
+ this._subscriptionCallbacksByServerSubscriptionId = {};
6006
+ this._subscriptionsByHash = {};
6007
+ this._subscriptionsAutoDisposedByRpc = new Set();
5937
6008
  let url = new URL(endpoint);
5938
6009
  const useHttps = url.protocol === 'https:';
5939
6010
  let wsEndpoint;
@@ -7701,6 +7772,8 @@ class Connection {
7701
7772
 
7702
7773
 
7703
7774
  _wsOnClose(code) {
7775
+ this._rpcWebSocketGeneration++;
7776
+
7704
7777
  if (this._rpcWebSocketHeartbeat) {
7705
7778
  clearInterval(this._rpcWebSocketHeartbeat);
7706
7779
  this._rpcWebSocketHeartbeat = null;
@@ -7714,85 +7787,20 @@ class Connection {
7714
7787
  } // implicit close, prepare subscriptions for auto-reconnect
7715
7788
 
7716
7789
 
7717
- this._resetSubscriptions();
7718
- }
7719
- /**
7720
- * @internal
7721
- */
7722
-
7723
-
7724
- async _subscribe(sub, rpcMethod, rpcArgs) {
7725
- if (sub.subscriptionId == null) {
7726
- sub.subscriptionId = 'subscribing';
7727
-
7728
- try {
7729
- const id = await this._rpcWebSocket.call(rpcMethod, rpcArgs);
7730
-
7731
- if (typeof id === 'number' && sub.subscriptionId === 'subscribing') {
7732
- // eslint-disable-next-line require-atomic-updates
7733
- sub.subscriptionId = id;
7734
- }
7735
- } catch (err) {
7736
- if (sub.subscriptionId === 'subscribing') {
7737
- // eslint-disable-next-line require-atomic-updates
7738
- sub.subscriptionId = null;
7739
- }
7740
-
7741
- if (err instanceof Error) {
7742
- console.error(`${rpcMethod} error for argument`, rpcArgs, err.message);
7743
- }
7744
- }
7745
- }
7746
- }
7747
- /**
7748
- * @internal
7749
- */
7750
-
7751
-
7752
- async _unsubscribe(sub, rpcMethod) {
7753
- const subscriptionId = sub.subscriptionId;
7754
-
7755
- if (subscriptionId != null && typeof subscriptionId != 'string') {
7756
- const unsubscribeId = subscriptionId;
7757
-
7758
- try {
7759
- await this._rpcWebSocket.call(rpcMethod, [unsubscribeId]);
7760
- } catch (err) {
7761
- if (err instanceof Error) {
7762
- console.error(`${rpcMethod} error:`, err.message);
7763
- }
7764
- }
7765
- }
7766
- }
7767
- /**
7768
- * @internal
7769
- */
7770
-
7771
-
7772
- _resetSubscriptions() {
7773
- Object.values(this._accountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7774
- Object.values(this._logsSubscriptions).forEach(s => s.subscriptionId = null);
7775
- Object.values(this._programAccountChangeSubscriptions).forEach(s => s.subscriptionId = null);
7776
- Object.values(this._rootSubscriptions).forEach(s => s.subscriptionId = null);
7777
- Object.values(this._signatureSubscriptions).forEach(s => s.subscriptionId = null);
7778
- Object.values(this._slotSubscriptions).forEach(s => s.subscriptionId = null);
7779
- Object.values(this._slotUpdateSubscriptions).forEach(s => s.subscriptionId = null);
7790
+ this._subscriptionCallbacksByServerSubscriptionId = {};
7791
+ Object.entries(this._subscriptionsByHash).forEach(([hash, subscription]) => {
7792
+ this._subscriptionsByHash[hash] = { ...subscription,
7793
+ state: 'pending'
7794
+ };
7795
+ });
7780
7796
  }
7781
7797
  /**
7782
7798
  * @internal
7783
7799
  */
7784
7800
 
7785
7801
 
7786
- _updateSubscriptions() {
7787
- const accountKeys = Object.keys(this._accountChangeSubscriptions).map(Number);
7788
- const programKeys = Object.keys(this._programAccountChangeSubscriptions).map(Number);
7789
- const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
7790
- const slotUpdateKeys = Object.keys(this._slotUpdateSubscriptions).map(Number);
7791
- const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
7792
- const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
7793
- const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
7794
-
7795
- if (accountKeys.length === 0 && programKeys.length === 0 && slotKeys.length === 0 && slotUpdateKeys.length === 0 && signatureKeys.length === 0 && rootKeys.length === 0 && logsKeys.length === 0) {
7802
+ async _updateSubscriptions() {
7803
+ if (Object.keys(this._subscriptionsByHash).length === 0) {
7796
7804
  if (this._rpcWebSocketConnected) {
7797
7805
  this._rpcWebSocketConnected = false;
7798
7806
  this._rpcWebSocketIdleTimeout = setTimeout(() => {
@@ -7824,60 +7832,167 @@ class Connection {
7824
7832
  return;
7825
7833
  }
7826
7834
 
7827
- for (let id of accountKeys) {
7828
- const sub = this._accountChangeSubscriptions[id];
7835
+ const activeWebSocketGeneration = this._rpcWebSocketGeneration;
7829
7836
 
7830
- this._subscribe(sub, 'accountSubscribe', this._buildArgs([sub.publicKey], sub.commitment, 'base64'));
7831
- }
7832
-
7833
- for (let id of programKeys) {
7834
- const sub = this._programAccountChangeSubscriptions[id];
7837
+ const isCurrentConnectionStillActive = () => {
7838
+ return activeWebSocketGeneration === this._rpcWebSocketGeneration;
7839
+ };
7835
7840
 
7836
- this._subscribe(sub, 'programSubscribe', this._buildArgs([sub.programId], sub.commitment, 'base64', {
7837
- filters: sub.filters
7838
- }));
7839
- }
7841
+ await Promise.all( // Don't be tempted to change this to `Object.entries`. We call
7842
+ // `_updateSubscriptions` recursively when processing the state,
7843
+ // so it's important that we look up the *current* version of
7844
+ // each subscription, every time we process a hash.
7845
+ Object.keys(this._subscriptionsByHash).map(async hash => {
7846
+ const subscription = this._subscriptionsByHash[hash];
7840
7847
 
7841
- for (let id of slotKeys) {
7842
- const sub = this._slotSubscriptions[id];
7848
+ if (subscription === undefined) {
7849
+ // This entry has since been deleted. Skip.
7850
+ return;
7851
+ }
7843
7852
 
7844
- this._subscribe(sub, 'slotSubscribe', []);
7845
- }
7853
+ switch (subscription.state) {
7854
+ case 'pending':
7855
+ case 'unsubscribed':
7856
+ if (subscription.callbacks.size === 0) {
7857
+ /**
7858
+ * You can end up here when:
7859
+ *
7860
+ * - a subscription has recently unsubscribed
7861
+ * without having new callbacks added to it
7862
+ * while the unsubscribe was in flight, or
7863
+ * - when a pending subscription has its
7864
+ * listeners removed before a request was
7865
+ * sent to the server.
7866
+ *
7867
+ * Being that nobody is interested in this
7868
+ * subscription any longer, delete it.
7869
+ */
7870
+ delete this._subscriptionsByHash[hash];
7871
+
7872
+ if (subscription.state === 'unsubscribed') {
7873
+ delete this._subscriptionCallbacksByServerSubscriptionId[subscription.serverSubscriptionId];
7874
+ }
7846
7875
 
7847
- for (let id of slotUpdateKeys) {
7848
- const sub = this._slotUpdateSubscriptions[id];
7876
+ await this._updateSubscriptions();
7877
+ return;
7878
+ }
7849
7879
 
7850
- this._subscribe(sub, 'slotsUpdatesSubscribe', []);
7851
- }
7880
+ await (async () => {
7881
+ const {
7882
+ args,
7883
+ method
7884
+ } = subscription;
7852
7885
 
7853
- for (let id of signatureKeys) {
7854
- const sub = this._signatureSubscriptions[id];
7855
- const args = [sub.signature];
7856
- if (sub.options) args.push(sub.options);
7886
+ try {
7887
+ this._subscriptionsByHash[hash] = { ...subscription,
7888
+ state: 'subscribing'
7889
+ };
7890
+ const serverSubscriptionId = await this._rpcWebSocket.call(method, args);
7891
+ this._subscriptionsByHash[hash] = { ...subscription,
7892
+ serverSubscriptionId,
7893
+ state: 'subscribed'
7894
+ };
7895
+ this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId] = subscription.callbacks;
7896
+ await this._updateSubscriptions();
7897
+ } catch (e) {
7898
+ if (e instanceof Error) {
7899
+ console.error(`${method} error for argument`, args, e.message);
7900
+ }
7901
+
7902
+ if (!isCurrentConnectionStillActive()) {
7903
+ return;
7904
+ } // TODO: Maybe add an 'errored' state or a retry limit?
7857
7905
 
7858
- this._subscribe(sub, 'signatureSubscribe', args);
7859
- }
7860
7906
 
7861
- for (let id of rootKeys) {
7862
- const sub = this._rootSubscriptions[id];
7907
+ this._subscriptionsByHash[hash] = { ...subscription,
7908
+ state: 'pending'
7909
+ };
7910
+ await this._updateSubscriptions();
7911
+ }
7912
+ })();
7913
+ break;
7863
7914
 
7864
- this._subscribe(sub, 'rootSubscribe', []);
7865
- }
7915
+ case 'subscribed':
7916
+ if (subscription.callbacks.size === 0) {
7917
+ // By the time we successfully set up a subscription
7918
+ // with the server, the client stopped caring about it.
7919
+ // Tear it down now.
7920
+ await (async () => {
7921
+ const {
7922
+ serverSubscriptionId,
7923
+ unsubscribeMethod
7924
+ } = subscription;
7925
+
7926
+ if (this._subscriptionsAutoDisposedByRpc.has(serverSubscriptionId)) {
7927
+ /**
7928
+ * Special case.
7929
+ * If we're dealing with a subscription that has been auto-
7930
+ * disposed by the RPC, then we can skip the RPC call to
7931
+ * tear down the subscription here.
7932
+ *
7933
+ * NOTE: There is a proposal to eliminate this special case, here:
7934
+ * https://github.com/solana-labs/solana/issues/18892
7935
+ */
7936
+ this._subscriptionsAutoDisposedByRpc.delete(serverSubscriptionId);
7937
+ } else {
7938
+ this._subscriptionsByHash[hash] = { ...subscription,
7939
+ state: 'unsubscribing'
7940
+ };
7941
+
7942
+ try {
7943
+ await this._rpcWebSocket.call(unsubscribeMethod, [serverSubscriptionId]);
7944
+ } catch (e) {
7945
+ if (e instanceof Error) {
7946
+ console.error(`${unsubscribeMethod} error:`, e.message);
7947
+ }
7948
+
7949
+ if (!isCurrentConnectionStillActive()) {
7950
+ return;
7951
+ } // TODO: Maybe add an 'errored' state or a retry limit?
7952
+
7953
+
7954
+ this._subscriptionsByHash[hash] = { ...subscription,
7955
+ state: 'subscribed'
7956
+ };
7957
+ await this._updateSubscriptions();
7958
+ return;
7959
+ }
7960
+ }
7866
7961
 
7867
- for (let id of logsKeys) {
7868
- const sub = this._logsSubscriptions[id];
7869
- let filter;
7962
+ this._subscriptionsByHash[hash] = { ...subscription,
7963
+ state: 'unsubscribed'
7964
+ };
7965
+ await this._updateSubscriptions();
7966
+ })();
7967
+ }
7870
7968
 
7871
- if (typeof sub.filter === 'object') {
7872
- filter = {
7873
- mentions: [sub.filter.toString()]
7874
- };
7875
- } else {
7876
- filter = sub.filter;
7969
+ break;
7877
7970
  }
7971
+ }));
7972
+ }
7973
+ /**
7974
+ * @internal
7975
+ */
7878
7976
 
7879
- this._subscribe(sub, 'logsSubscribe', this._buildArgs([filter], sub.commitment));
7977
+
7978
+ _handleServerNotification(serverSubscriptionId, callbackArgs) {
7979
+ const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId];
7980
+
7981
+ if (callbacks === undefined) {
7982
+ return;
7880
7983
  }
7984
+
7985
+ callbacks.forEach(cb => {
7986
+ try {
7987
+ cb( // I failed to find a way to convince TypeScript that `cb` is of type
7988
+ // `TCallback` which is certainly compatible with `Parameters<TCallback>`.
7989
+ // See https://github.com/microsoft/TypeScript/issues/47615
7990
+ // @ts-ignore
7991
+ ...callbackArgs);
7992
+ } catch (e) {
7993
+ console.error(e);
7994
+ }
7995
+ });
7881
7996
  }
7882
7997
  /**
7883
7998
  * @internal
@@ -7885,14 +8000,71 @@ class Connection {
7885
8000
 
7886
8001
 
7887
8002
  _wsOnAccountNotification(notification) {
7888
- const res = superstruct.create(notification, AccountNotificationResult);
8003
+ const {
8004
+ result,
8005
+ subscription
8006
+ } = superstruct.create(notification, AccountNotificationResult);
7889
8007
 
7890
- for (const sub of Object.values(this._accountChangeSubscriptions)) {
7891
- if (sub.subscriptionId === res.subscription) {
7892
- sub.callback(res.result.value, res.result.context);
7893
- return;
7894
- }
8008
+ this._handleServerNotification(subscription, [result.value, result.context]);
8009
+ }
8010
+ /**
8011
+ * @internal
8012
+ */
8013
+
8014
+
8015
+ _makeSubscription(subscriptionConfig,
8016
+ /**
8017
+ * When preparing `args` for a call to `_makeSubscription`, be sure
8018
+ * to carefully apply a default `commitment` property, if necessary.
8019
+ *
8020
+ * - If the user supplied a `commitment` use that.
8021
+ * - Otherwise, if the `Connection::commitment` is set, use that.
8022
+ * - Otherwise, set it to the RPC server default: `finalized`.
8023
+ *
8024
+ * This is extremely important to ensure that these two fundamentally
8025
+ * identical subscriptions produce the same identifying hash:
8026
+ *
8027
+ * - A subscription made without specifying a commitment.
8028
+ * - A subscription made where the commitment specified is the same
8029
+ * as the default applied to the subscription above.
8030
+ *
8031
+ * Example; these two subscriptions must produce the same hash:
8032
+ *
8033
+ * - An `accountSubscribe` subscription for `'PUBKEY'`
8034
+ * - An `accountSubscribe` subscription for `'PUBKEY'` with commitment
8035
+ * `'finalized'`.
8036
+ *
8037
+ * See the 'making a subscription with defaulted params omitted' test
8038
+ * in `connection-subscriptions.ts` for more.
8039
+ */
8040
+ args) {
8041
+ const clientSubscriptionId = this._nextClientSubscriptionId++;
8042
+ const hash = fastStableStringify$1([subscriptionConfig.method, args], true
8043
+ /* isArrayProp */
8044
+ );
8045
+ const existingSubscription = this._subscriptionsByHash[hash];
8046
+
8047
+ if (existingSubscription === undefined) {
8048
+ this._subscriptionsByHash[hash] = { ...subscriptionConfig,
8049
+ args,
8050
+ callbacks: new Set([subscriptionConfig.callback]),
8051
+ state: 'pending'
8052
+ };
8053
+ } else {
8054
+ existingSubscription.callbacks.add(subscriptionConfig.callback);
7895
8055
  }
8056
+
8057
+ this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId] = async () => {
8058
+ delete this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
8059
+ const subscription = this._subscriptionsByHash[hash];
8060
+ assert(subscription !== undefined, `Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`);
8061
+ subscription.callbacks.delete(subscriptionConfig.callback);
8062
+ await this._updateSubscriptions();
8063
+ };
8064
+
8065
+ this._updateSubscriptions();
8066
+
8067
+ return clientSubscriptionId;
7896
8068
  }
7897
8069
  /**
7898
8070
  * Register a callback to be invoked whenever the specified account changes
@@ -7905,35 +8077,24 @@ class Connection {
7905
8077
 
7906
8078
 
7907
8079
  onAccountChange(publicKey, callback, commitment) {
7908
- const id = ++this._accountChangeSubscriptionCounter;
7909
- this._accountChangeSubscriptions[id] = {
7910
- publicKey: publicKey.toBase58(),
7911
- callback,
7912
- commitment,
7913
- subscriptionId: null
7914
- };
8080
+ const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
8081
+ 'base64');
7915
8082
 
7916
- this._updateSubscriptions();
7917
-
7918
- return id;
8083
+ return this._makeSubscription({
8084
+ callback,
8085
+ method: 'accountSubscribe',
8086
+ unsubscribeMethod: 'accountUnsubscribe'
8087
+ }, args);
7919
8088
  }
7920
8089
  /**
7921
8090
  * Deregister an account notification callback
7922
8091
  *
7923
- * @param id subscription id to deregister
8092
+ * @param id client subscription id to deregister
7924
8093
  */
7925
8094
 
7926
8095
 
7927
- async removeAccountChangeListener(id) {
7928
- if (this._accountChangeSubscriptions[id]) {
7929
- const subInfo = this._accountChangeSubscriptions[id];
7930
- delete this._accountChangeSubscriptions[id];
7931
- await this._unsubscribe(subInfo, 'accountUnsubscribe');
7932
-
7933
- this._updateSubscriptions();
7934
- } else {
7935
- console.warn(createSubscriptionWarningMessage(id, 'account change'));
7936
- }
8096
+ async removeAccountChangeListener(clientSubscriptionId) {
8097
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change');
7937
8098
  }
7938
8099
  /**
7939
8100
  * @internal
@@ -7941,21 +8102,15 @@ class Connection {
7941
8102
 
7942
8103
 
7943
8104
  _wsOnProgramAccountNotification(notification) {
7944
- const res = superstruct.create(notification, ProgramAccountNotificationResult);
8105
+ const {
8106
+ result,
8107
+ subscription
8108
+ } = superstruct.create(notification, ProgramAccountNotificationResult);
7945
8109
 
7946
- for (const sub of Object.values(this._programAccountChangeSubscriptions)) {
7947
- if (sub.subscriptionId === res.subscription) {
7948
- const {
7949
- value,
7950
- context
7951
- } = res.result;
7952
- sub.callback({
7953
- accountId: value.pubkey,
7954
- accountInfo: value.account
7955
- }, context);
7956
- return;
7957
- }
7958
- }
8110
+ this._handleServerNotification(subscription, [{
8111
+ accountId: result.value.pubkey,
8112
+ accountInfo: result.value.account
8113
+ }, result.context]);
7959
8114
  }
7960
8115
  /**
7961
8116
  * Register a callback to be invoked whenever accounts owned by the
@@ -7970,36 +8125,30 @@ class Connection {
7970
8125
 
7971
8126
 
7972
8127
  onProgramAccountChange(programId, callback, commitment, filters) {
7973
- const id = ++this._programAccountChangeSubscriptionCounter;
7974
- this._programAccountChangeSubscriptions[id] = {
7975
- programId: programId.toBase58(),
8128
+ const args = this._buildArgs([programId.toBase58()], commitment || this._commitment || 'finalized', // Apply connection/server default.
8129
+ 'base64'
8130
+ /* encoding */
8131
+ , filters ? {
8132
+ filters: filters
8133
+ } : undefined
8134
+ /* extra */
8135
+ );
8136
+
8137
+ return this._makeSubscription({
7976
8138
  callback,
7977
- commitment,
7978
- subscriptionId: null,
7979
- filters
7980
- };
7981
-
7982
- this._updateSubscriptions();
7983
-
7984
- return id;
8139
+ method: 'programSubscribe',
8140
+ unsubscribeMethod: 'programUnsubscribe'
8141
+ }, args);
7985
8142
  }
7986
8143
  /**
7987
8144
  * Deregister an account notification callback
7988
8145
  *
7989
- * @param id subscription id to deregister
8146
+ * @param id client subscription id to deregister
7990
8147
  */
7991
8148
 
7992
8149
 
7993
- async removeProgramAccountChangeListener(id) {
7994
- if (this._programAccountChangeSubscriptions[id]) {
7995
- const subInfo = this._programAccountChangeSubscriptions[id];
7996
- delete this._programAccountChangeSubscriptions[id];
7997
- await this._unsubscribe(subInfo, 'programUnsubscribe');
7998
-
7999
- this._updateSubscriptions();
8000
- } else {
8001
- console.warn(createSubscriptionWarningMessage(id, 'program account change'));
8002
- }
8150
+ async removeProgramAccountChangeListener(clientSubscriptionId) {
8151
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change');
8003
8152
  }
8004
8153
  /**
8005
8154
  * Registers a callback to be invoked whenever logs are emitted.
@@ -8007,35 +8156,26 @@ class Connection {
8007
8156
 
8008
8157
 
8009
8158
  onLogs(filter, callback, commitment) {
8010
- const id = ++this._logsSubscriptionCounter;
8011
- this._logsSubscriptions[id] = {
8012
- filter,
8013
- callback,
8014
- commitment,
8015
- subscriptionId: null
8016
- };
8159
+ const args = this._buildArgs([typeof filter === 'object' ? {
8160
+ mentions: [filter.toString()]
8161
+ } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default.
8162
+ );
8017
8163
 
8018
- this._updateSubscriptions();
8019
-
8020
- return id;
8164
+ return this._makeSubscription({
8165
+ callback,
8166
+ method: 'logsSubscribe',
8167
+ unsubscribeMethod: 'logsUnsubscribe'
8168
+ }, args);
8021
8169
  }
8022
8170
  /**
8023
8171
  * Deregister a logs callback.
8024
8172
  *
8025
- * @param id subscription id to deregister.
8173
+ * @param id client subscription id to deregister.
8026
8174
  */
8027
8175
 
8028
8176
 
8029
- async removeOnLogsListener(id) {
8030
- if (this._logsSubscriptions[id]) {
8031
- const subInfo = this._logsSubscriptions[id];
8032
- delete this._logsSubscriptions[id];
8033
- await this._unsubscribe(subInfo, 'logsUnsubscribe');
8034
-
8035
- this._updateSubscriptions();
8036
- } else {
8037
- console.warn(createSubscriptionWarningMessage(id, 'logs'));
8038
- }
8177
+ async removeOnLogsListener(clientSubscriptionId) {
8178
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs');
8039
8179
  }
8040
8180
  /**
8041
8181
  * @internal
@@ -8043,17 +8183,12 @@ class Connection {
8043
8183
 
8044
8184
 
8045
8185
  _wsOnLogsNotification(notification) {
8046
- const res = superstruct.create(notification, LogsNotificationResult);
8047
- const keys = Object.keys(this._logsSubscriptions).map(Number);
8048
-
8049
- for (let id of keys) {
8050
- const sub = this._logsSubscriptions[id];
8186
+ const {
8187
+ result,
8188
+ subscription
8189
+ } = superstruct.create(notification, LogsNotificationResult);
8051
8190
 
8052
- if (sub.subscriptionId === res.subscription) {
8053
- sub.callback(res.result.value, res.result.context);
8054
- return;
8055
- }
8056
- }
8191
+ this._handleServerNotification(subscription, [result.value, result.context]);
8057
8192
  }
8058
8193
  /**
8059
8194
  * @internal
@@ -8061,14 +8196,12 @@ class Connection {
8061
8196
 
8062
8197
 
8063
8198
  _wsOnSlotNotification(notification) {
8064
- const res = superstruct.create(notification, SlotNotificationResult);
8199
+ const {
8200
+ result,
8201
+ subscription
8202
+ } = superstruct.create(notification, SlotNotificationResult);
8065
8203
 
8066
- for (const sub of Object.values(this._slotSubscriptions)) {
8067
- if (sub.subscriptionId === res.subscription) {
8068
- sub.callback(res.result);
8069
- return;
8070
- }
8071
- }
8204
+ this._handleServerNotification(subscription, [result]);
8072
8205
  }
8073
8206
  /**
8074
8207
  * Register a callback to be invoked upon slot changes
@@ -8079,33 +8212,23 @@ class Connection {
8079
8212
 
8080
8213
 
8081
8214
  onSlotChange(callback) {
8082
- const id = ++this._slotSubscriptionCounter;
8083
- this._slotSubscriptions[id] = {
8215
+ return this._makeSubscription({
8084
8216
  callback,
8085
- subscriptionId: null
8086
- };
8087
-
8088
- this._updateSubscriptions();
8089
-
8090
- return id;
8217
+ method: 'slotSubscribe',
8218
+ unsubscribeMethod: 'slotUnsubscribe'
8219
+ }, []
8220
+ /* args */
8221
+ );
8091
8222
  }
8092
8223
  /**
8093
8224
  * Deregister a slot notification callback
8094
8225
  *
8095
- * @param id subscription id to deregister
8226
+ * @param id client subscription id to deregister
8096
8227
  */
8097
8228
 
8098
8229
 
8099
- async removeSlotChangeListener(id) {
8100
- if (this._slotSubscriptions[id]) {
8101
- const subInfo = this._slotSubscriptions[id];
8102
- delete this._slotSubscriptions[id];
8103
- await this._unsubscribe(subInfo, 'slotUnsubscribe');
8104
-
8105
- this._updateSubscriptions();
8106
- } else {
8107
- console.warn(createSubscriptionWarningMessage(id, 'slot change'));
8108
- }
8230
+ async removeSlotChangeListener(clientSubscriptionId) {
8231
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change');
8109
8232
  }
8110
8233
  /**
8111
8234
  * @internal
@@ -8113,14 +8236,12 @@ class Connection {
8113
8236
 
8114
8237
 
8115
8238
  _wsOnSlotUpdatesNotification(notification) {
8116
- const res = superstruct.create(notification, SlotUpdateNotificationResult);
8239
+ const {
8240
+ result,
8241
+ subscription
8242
+ } = superstruct.create(notification, SlotUpdateNotificationResult);
8117
8243
 
8118
- for (const sub of Object.values(this._slotUpdateSubscriptions)) {
8119
- if (sub.subscriptionId === res.subscription) {
8120
- sub.callback(res.result);
8121
- return;
8122
- }
8123
- }
8244
+ this._handleServerNotification(subscription, [result]);
8124
8245
  }
8125
8246
  /**
8126
8247
  * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
@@ -8132,32 +8253,36 @@ class Connection {
8132
8253
 
8133
8254
 
8134
8255
  onSlotUpdate(callback) {
8135
- const id = ++this._slotUpdateSubscriptionCounter;
8136
- this._slotUpdateSubscriptions[id] = {
8256
+ return this._makeSubscription({
8137
8257
  callback,
8138
- subscriptionId: null
8139
- };
8140
-
8141
- this._updateSubscriptions();
8142
-
8143
- return id;
8258
+ method: 'slotsUpdatesSubscribe',
8259
+ unsubscribeMethod: 'slotsUpdatesUnsubscribe'
8260
+ }, []
8261
+ /* args */
8262
+ );
8144
8263
  }
8145
8264
  /**
8146
8265
  * Deregister a slot update notification callback
8147
8266
  *
8148
- * @param id subscription id to deregister
8267
+ * @param id client subscription id to deregister
8149
8268
  */
8150
8269
 
8151
8270
 
8152
- async removeSlotUpdateListener(id) {
8153
- if (this._slotUpdateSubscriptions[id]) {
8154
- const subInfo = this._slotUpdateSubscriptions[id];
8155
- delete this._slotUpdateSubscriptions[id];
8156
- await this._unsubscribe(subInfo, 'slotsUpdatesUnsubscribe');
8271
+ async removeSlotUpdateListener(clientSubscriptionId) {
8272
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update');
8273
+ }
8274
+ /**
8275
+ * @internal
8276
+ */
8157
8277
 
8158
- this._updateSubscriptions();
8278
+
8279
+ async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) {
8280
+ const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId];
8281
+
8282
+ if (dispose) {
8283
+ await dispose();
8159
8284
  } else {
8160
- console.warn(createSubscriptionWarningMessage(id, 'slot update'));
8285
+ console.warn('Ignored unsubscribe request because an active subscription with id ' + `\`${clientSubscriptionId}\` for '${subscriptionName}' events ` + 'could not be found.');
8161
8286
  }
8162
8287
  }
8163
8288
 
@@ -8204,30 +8329,34 @@ class Connection {
8204
8329
 
8205
8330
 
8206
8331
  _wsOnSignatureNotification(notification) {
8207
- const res = superstruct.create(notification, SignatureNotificationResult);
8208
-
8209
- for (const [id, sub] of Object.entries(this._signatureSubscriptions)) {
8210
- if (sub.subscriptionId === res.subscription) {
8211
- if (res.result.value === 'receivedSignature') {
8212
- sub.callback({
8213
- type: 'received'
8214
- }, res.result.context);
8215
- } else {
8216
- // Signatures subscriptions are auto-removed by the RPC service so
8217
- // no need to explicitly send an unsubscribe message
8218
- delete this._signatureSubscriptions[Number(id)];
8219
-
8220
- this._updateSubscriptions();
8221
-
8222
- sub.callback({
8223
- type: 'status',
8224
- result: res.result.value
8225
- }, res.result.context);
8226
- }
8227
-
8228
- return;
8229
- }
8230
- }
8332
+ const {
8333
+ result,
8334
+ subscription
8335
+ } = superstruct.create(notification, SignatureNotificationResult);
8336
+
8337
+ if (result.value !== 'receivedSignature') {
8338
+ /**
8339
+ * Special case.
8340
+ * After a signature is processed, RPCs automatically dispose of the
8341
+ * subscription on the server side. We need to track which of these
8342
+ * subscriptions have been disposed in such a way, so that we know
8343
+ * whether the client is dealing with a not-yet-processed signature
8344
+ * (in which case we must tear down the server subscription) or an
8345
+ * already-processed signature (in which case the client can simply
8346
+ * clear out the subscription locally without telling the server).
8347
+ *
8348
+ * NOTE: There is a proposal to eliminate this special case, here:
8349
+ * https://github.com/solana-labs/solana/issues/18892
8350
+ */
8351
+ this._subscriptionsAutoDisposedByRpc.add(subscription);
8352
+ }
8353
+
8354
+ this._handleServerNotification(subscription, result.value === 'receivedSignature' ? [{
8355
+ type: 'received'
8356
+ }, result.context] : [{
8357
+ type: 'status',
8358
+ result: result.value
8359
+ }, result.context]);
8231
8360
  }
8232
8361
  /**
8233
8362
  * Register a callback to be invoked upon signature updates
@@ -8240,23 +8369,26 @@ class Connection {
8240
8369
 
8241
8370
 
8242
8371
  onSignature(signature, callback, commitment) {
8243
- const id = ++this._signatureSubscriptionCounter;
8244
- this._signatureSubscriptions[id] = {
8245
- signature,
8372
+ const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default.
8373
+ );
8374
+
8375
+ const clientSubscriptionId = this._makeSubscription({
8246
8376
  callback: (notification, context) => {
8247
8377
  if (notification.type === 'status') {
8248
- callback(notification.result, context);
8378
+ callback(notification.result, context); // Signatures subscriptions are auto-removed by the RPC service
8379
+ // so no need to explicitly send an unsubscribe message.
8380
+
8381
+ try {
8382
+ this.removeSignatureListener(clientSubscriptionId); // eslint-disable-next-line no-empty
8383
+ } catch {// Already removed.
8384
+ }
8249
8385
  }
8250
8386
  },
8251
- options: {
8252
- commitment
8253
- },
8254
- subscriptionId: null
8255
- };
8256
-
8257
- this._updateSubscriptions();
8387
+ method: 'signatureSubscribe',
8388
+ unsubscribeMethod: 'signatureUnsubscribe'
8389
+ }, args);
8258
8390
 
8259
- return id;
8391
+ return clientSubscriptionId;
8260
8392
  }
8261
8393
  /**
8262
8394
  * Register a callback to be invoked when a transaction is
@@ -8271,35 +8403,43 @@ class Connection {
8271
8403
 
8272
8404
 
8273
8405
  onSignatureWithOptions(signature, callback, options) {
8274
- const id = ++this._signatureSubscriptionCounter;
8275
- this._signatureSubscriptions[id] = {
8276
- signature,
8277
- callback,
8278
- options,
8279
- subscriptionId: null
8406
+ const {
8407
+ commitment,
8408
+ ...extra
8409
+ } = { ...options,
8410
+ commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default.
8411
+
8280
8412
  };
8281
8413
 
8282
- this._updateSubscriptions();
8414
+ const args = this._buildArgs([signature], commitment, undefined
8415
+ /* encoding */
8416
+ , extra);
8417
+
8418
+ const clientSubscriptionId = this._makeSubscription({
8419
+ callback: (notification, context) => {
8420
+ callback(notification, context); // Signatures subscriptions are auto-removed by the RPC service
8421
+ // so no need to explicitly send an unsubscribe message.
8422
+
8423
+ try {
8424
+ this.removeSignatureListener(clientSubscriptionId); // eslint-disable-next-line no-empty
8425
+ } catch {// Already removed.
8426
+ }
8427
+ },
8428
+ method: 'signatureSubscribe',
8429
+ unsubscribeMethod: 'signatureUnsubscribe'
8430
+ }, args);
8283
8431
 
8284
- return id;
8432
+ return clientSubscriptionId;
8285
8433
  }
8286
8434
  /**
8287
8435
  * Deregister a signature notification callback
8288
8436
  *
8289
- * @param id subscription id to deregister
8437
+ * @param id client subscription id to deregister
8290
8438
  */
8291
8439
 
8292
8440
 
8293
- async removeSignatureListener(id) {
8294
- if (this._signatureSubscriptions[id]) {
8295
- const subInfo = this._signatureSubscriptions[id];
8296
- delete this._signatureSubscriptions[id];
8297
- await this._unsubscribe(subInfo, 'signatureUnsubscribe');
8298
-
8299
- this._updateSubscriptions();
8300
- } else {
8301
- console.warn(createSubscriptionWarningMessage(id, 'signature result'));
8302
- }
8441
+ async removeSignatureListener(clientSubscriptionId) {
8442
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result');
8303
8443
  }
8304
8444
  /**
8305
8445
  * @internal
@@ -8307,14 +8447,12 @@ class Connection {
8307
8447
 
8308
8448
 
8309
8449
  _wsOnRootNotification(notification) {
8310
- const res = superstruct.create(notification, RootNotificationResult);
8450
+ const {
8451
+ result,
8452
+ subscription
8453
+ } = superstruct.create(notification, RootNotificationResult);
8311
8454
 
8312
- for (const sub of Object.values(this._rootSubscriptions)) {
8313
- if (sub.subscriptionId === res.subscription) {
8314
- sub.callback(res.result);
8315
- return;
8316
- }
8317
- }
8455
+ this._handleServerNotification(subscription, [result]);
8318
8456
  }
8319
8457
  /**
8320
8458
  * Register a callback to be invoked upon root changes
@@ -8325,33 +8463,23 @@ class Connection {
8325
8463
 
8326
8464
 
8327
8465
  onRootChange(callback) {
8328
- const id = ++this._rootSubscriptionCounter;
8329
- this._rootSubscriptions[id] = {
8466
+ return this._makeSubscription({
8330
8467
  callback,
8331
- subscriptionId: null
8332
- };
8333
-
8334
- this._updateSubscriptions();
8335
-
8336
- return id;
8468
+ method: 'rootSubscribe',
8469
+ unsubscribeMethod: 'rootUnsubscribe'
8470
+ }, []
8471
+ /* args */
8472
+ );
8337
8473
  }
8338
8474
  /**
8339
8475
  * Deregister a root notification callback
8340
8476
  *
8341
- * @param id subscription id to deregister
8477
+ * @param id client subscription id to deregister
8342
8478
  */
8343
8479
 
8344
8480
 
8345
- async removeRootChangeListener(id) {
8346
- if (this._rootSubscriptions[id]) {
8347
- const subInfo = this._rootSubscriptions[id];
8348
- delete this._rootSubscriptions[id];
8349
- await this._unsubscribe(subInfo, 'rootUnsubscribe');
8350
-
8351
- this._updateSubscriptions();
8352
- } else {
8353
- console.warn(createSubscriptionWarningMessage(id, 'root change'));
8354
- }
8481
+ async removeRootChangeListener(clientSubscriptionId) {
8482
+ await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change');
8355
8483
  }
8356
8484
 
8357
8485
  }
@@ -10055,6 +10183,7 @@ exports.NONCE_ACCOUNT_LENGTH = NONCE_ACCOUNT_LENGTH;
10055
10183
  exports.NonceAccount = NonceAccount;
10056
10184
  exports.PACKET_DATA_SIZE = PACKET_DATA_SIZE;
10057
10185
  exports.PublicKey = PublicKey;
10186
+ exports.SIGNATURE_LENGTH_IN_BYTES = SIGNATURE_LENGTH_IN_BYTES;
10058
10187
  exports.SOLANA_SCHEMA = SOLANA_SCHEMA;
10059
10188
  exports.STAKE_CONFIG_ID = STAKE_CONFIG_ID;
10060
10189
  exports.STAKE_INSTRUCTION_LAYOUTS = STAKE_INSTRUCTION_LAYOUTS;