starknet 10.6.7 → 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7292,6 +7292,8 @@ var Subscription = class {
7292
7292
  maxBufferSize;
7293
7293
  handler = null;
7294
7294
  _isClosed = false;
7295
+ // The unsubscribe request currently on the wire, shared by concurrent callers.
7296
+ pendingUnsubscribe = null;
7295
7297
  /**
7296
7298
  * @internal
7297
7299
  * @param options - Subscription configuration options
@@ -7310,6 +7312,20 @@ var Subscription = class {
7310
7312
  get isClosed() {
7311
7313
  return this._isClosed;
7312
7314
  }
7315
+ /**
7316
+ * Closes the subscription locally, without contacting the node.
7317
+ *
7318
+ * Used when the channel knows the subscription is gone and cannot be recovered — a
7319
+ * re-subscribe refused after a reconnection, for instance. Without it the object would keep
7320
+ * reporting itself as open while no event could ever reach its handler again.
7321
+ * @internal
7322
+ */
7323
+ _markClosed() {
7324
+ if (this._isClosed) return;
7325
+ this._isClosed = true;
7326
+ this.events.emit("unsubscribe", void 0);
7327
+ this.events.clear();
7328
+ }
7313
7329
  /**
7314
7330
  * Internal method to handle incoming events from the WebSocket channel.
7315
7331
  * If a handler is attached, it's invoked immediately. Otherwise, the event is buffered.
@@ -7356,14 +7372,24 @@ var Subscription = class {
7356
7372
  if (this._isClosed) {
7357
7373
  return true;
7358
7374
  }
7359
- const success = await this.channel.unsubscribe(this.id);
7360
- if (success) {
7361
- this._isClosed = true;
7362
- this.channel.removeSubscription(this.id);
7363
- this.events.emit("unsubscribe", void 0);
7364
- this.events.clear();
7375
+ if (this.pendingUnsubscribe) {
7376
+ return this.pendingUnsubscribe;
7365
7377
  }
7366
- return success;
7378
+ this.pendingUnsubscribe = (async () => {
7379
+ try {
7380
+ const success = await this.channel.unsubscribe(this.id);
7381
+ if (success) {
7382
+ this._isClosed = true;
7383
+ this.channel.removeSubscription(this.id);
7384
+ this.events.emit("unsubscribe", void 0);
7385
+ this.events.clear();
7386
+ }
7387
+ return success;
7388
+ } finally {
7389
+ this.pendingUnsubscribe = null;
7390
+ }
7391
+ })();
7392
+ return this.pendingUnsubscribe;
7367
7393
  }
7368
7394
  };
7369
7395
 
@@ -7396,6 +7422,15 @@ var WebSocketChannel = class {
7396
7422
  requestQueue = [];
7397
7423
  /** Abort handles for the requests currently on the wire, one per pending `sendReceive`. */
7398
7424
  inFlight = /* @__PURE__ */ new Set();
7425
+ /**
7426
+ * Callers blocked in `waitForUnsubscription`, keyed by subscription id.
7427
+ *
7428
+ * Held here rather than as `unsubscribe` event listeners because that event only ever
7429
+ * announces success: a waiter attached to it cannot learn that the node refused the
7430
+ * unsubscribe or that the connection went away, and would wait forever with no timeout of
7431
+ * its own to fall back on.
7432
+ */
7433
+ unsubscribeWaiters = /* @__PURE__ */ new Map();
7399
7434
  events = new EventEmitter();
7400
7435
  openListener = (ev) => {
7401
7436
  this.scheduleReconnectAttemptsReset();
@@ -7592,6 +7627,7 @@ var WebSocketChannel = class {
7592
7627
  this.isReconnecting = false;
7593
7628
  this._rejectRequestQueue("the connection was closed by the user");
7594
7629
  this._rejectInFlight("the connection was closed by the user");
7630
+ this._rejectUnsubscribeWaiters("the connection was closed by the user");
7595
7631
  this.websocket.close(code, reason);
7596
7632
  }
7597
7633
  /**
@@ -7616,14 +7652,48 @@ var WebSocketChannel = class {
7616
7652
  * @returns {Promise<boolean>} A Promise that resolves with `true` if the unsubscription was successful.
7617
7653
  */
7618
7654
  async unsubscribe(subscriptionId) {
7619
- const status = await this.sendReceive("starknet_unsubscribe", {
7620
- subscription_id: subscriptionId
7621
- });
7655
+ let status;
7656
+ try {
7657
+ status = await this.sendReceive("starknet_unsubscribe", {
7658
+ subscription_id: subscriptionId
7659
+ });
7660
+ } catch (error) {
7661
+ this._settleUnsubscribeWaiters(subscriptionId, error);
7662
+ throw error;
7663
+ }
7622
7664
  if (status) {
7623
7665
  this.events.emit("unsubscribe", subscriptionId);
7666
+ this._settleUnsubscribeWaiters(subscriptionId);
7667
+ } else {
7668
+ this._settleUnsubscribeWaiters(
7669
+ subscriptionId,
7670
+ new Error(`Node refused to unsubscribe subscription ${subscriptionId}`)
7671
+ );
7624
7672
  }
7625
7673
  return status;
7626
7674
  }
7675
+ /** Settles every caller waiting on one subscription id: resolved, or rejected with `error`. */
7676
+ _settleUnsubscribeWaiters(subscriptionId, error) {
7677
+ const waiters = this.unsubscribeWaiters.get(subscriptionId);
7678
+ if (!waiters) return;
7679
+ this.unsubscribeWaiters.delete(subscriptionId);
7680
+ waiters.forEach((waiter) => error ? waiter.reject(error) : waiter.resolve());
7681
+ }
7682
+ /**
7683
+ * Rejects every caller still waiting on any subscription.
7684
+ *
7685
+ * Once the connection is gone, no unsubscribe can be observed on it: a reconnection restores
7686
+ * each subscription under a fresh id, so the id being waited on will never be announced.
7687
+ */
7688
+ _rejectUnsubscribeWaiters(reason) {
7689
+ if (this.unsubscribeWaiters.size === 0) return;
7690
+ Array.from(this.unsubscribeWaiters.keys()).forEach(
7691
+ (id) => this._settleUnsubscribeWaiters(
7692
+ id,
7693
+ new WebSocketNotConnectedError(`Subscription ${id} was never unsubscribed: ${reason}`)
7694
+ )
7695
+ );
7696
+ }
7627
7697
  /**
7628
7698
  * Returns a Promise that resolves when a specific subscription is successfully unsubscribed.
7629
7699
  * @param {SUBSCRIPTION_ID} targetId - The ID of the subscription to wait for.
@@ -7635,14 +7705,10 @@ var WebSocketChannel = class {
7635
7705
  * ```
7636
7706
  */
7637
7707
  waitForUnsubscription(targetId) {
7638
- return new Promise((resolve) => {
7639
- const listener = (unsubId) => {
7640
- if (unsubId === targetId) {
7641
- this.events.off("unsubscribe", listener);
7642
- resolve();
7643
- }
7644
- };
7645
- this.events.on("unsubscribe", listener);
7708
+ return new Promise((resolve, reject) => {
7709
+ const waiters = this.unsubscribeWaiters.get(targetId) ?? /* @__PURE__ */ new Set();
7710
+ waiters.add({ resolve, reject });
7711
+ this.unsubscribeWaiters.set(targetId, waiters);
7646
7712
  });
7647
7713
  }
7648
7714
  /**
@@ -7709,6 +7775,7 @@ var WebSocketChannel = class {
7709
7775
  logger.info(`Subscription ${sub.method} restored with new ID: ${newSubId}`);
7710
7776
  } catch (error) {
7711
7777
  logger.error(`Failed to restore subscription ${sub.method}:`, error);
7778
+ sub._markClosed();
7712
7779
  }
7713
7780
  });
7714
7781
  await Promise.all(restorePromises);
@@ -7778,6 +7845,7 @@ var WebSocketChannel = class {
7778
7845
  this.websocket.removeEventListener("message", this.messageListener);
7779
7846
  this.websocket.removeEventListener("error", this.errorListener);
7780
7847
  this._rejectInFlight("the connection was closed");
7848
+ this._rejectUnsubscribeWaiters("the connection was closed");
7781
7849
  this.events.emit("close", ev);
7782
7850
  if (!this.userInitiatedClose) {
7783
7851
  this._startReconnect();
@@ -7799,9 +7867,16 @@ var WebSocketChannel = class {
7799
7867
  if (subscription) {
7800
7868
  subscription._handleEvent(result);
7801
7869
  } else {
7802
- logger.warn(
7803
- `WebSocketChannel: Received event for untracked subscription ID: ${subscription_id}.`
7804
- );
7870
+ queueMicrotask(() => {
7871
+ const registered = this.activeSubscriptions.get(subscription_id);
7872
+ if (registered) {
7873
+ registered._handleEvent(result);
7874
+ } else {
7875
+ logger.warn(
7876
+ `WebSocketChannel: Received event for untracked subscription ID: ${subscription_id}.`
7877
+ );
7878
+ }
7879
+ });
7805
7880
  }
7806
7881
  }
7807
7882
  logger.debug("onMessageProxy:", event.data);
@@ -12852,7 +12927,7 @@ function fromWalletApiCall(call) {
12852
12927
  }
12853
12928
  function toWalletApiActions(actions) {
12854
12929
  return actions.map(
12855
- (action) => action.type === "subaccount_invoke" ? { ...action, calls: action.calls.map(toWalletApiCall) } : action
12930
+ (action) => action.type === "shadow_account_invoke" ? { ...action, calls: action.calls.map(toWalletApiCall) } : action
12856
12931
  );
12857
12932
  }
12858
12933
 
@@ -12871,7 +12946,7 @@ __export(connectV6_exports, {
12871
12946
  strk20Balances: () => strk20Balances,
12872
12947
  strk20InvokeTransaction: () => strk20InvokeTransaction,
12873
12948
  strk20PrepareInvoke: () => strk20PrepareInvoke,
12874
- strk20SubaccountCommitment: () => strk20SubaccountCommitment,
12949
+ strk20ShadowAccountCommitment: () => strk20ShadowAccountCommitment,
12875
12950
  subscribeWalletEvent: () => subscribeWalletEvent2,
12876
12951
  supportedSpecs: () => supportedSpecs3,
12877
12952
  supportedWalletApi: () => supportedWalletApi3,
@@ -12959,9 +13034,9 @@ function strk20InvokeTransaction(walletWSF, actions) {
12959
13034
  params: { actions }
12960
13035
  });
12961
13036
  }
12962
- function strk20SubaccountCommitment(walletWSF, dapp_name, nonce) {
13037
+ function strk20ShadowAccountCommitment(walletWSF, dapp_name, nonce) {
12963
13038
  return walletWSF.features["starknet:walletApi"].request({
12964
- type: "wallet_strk20SubaccountCommitment",
13039
+ type: "wallet_strk20ShadowAccountCommitment",
12965
13040
  params: { dapp_name, nonce }
12966
13041
  });
12967
13042
  }
@@ -13053,25 +13128,25 @@ var WalletAccountV6 = class _WalletAccountV6 extends WalletAccountV5 {
13053
13128
  return strk20InvokeTransaction(this.v6Provider, toWalletApiActions(actions));
13054
13129
  }
13055
13130
  /**
13056
- * Compute the commitment of a DAPP STRK20 sub-account. The commitment is computed
13131
+ * Compute the commitment of a DAPP STRK20 shadow account. The commitment is computed
13057
13132
  * locally by the wallet from the user private state ; no transaction is sent.
13058
13133
  *
13059
- * When `nonce` is given, the full commitment of this single sub-account is returned.
13134
+ * When `nonce` is given, the full commitment of this single shadow account is returned.
13060
13135
  * When `nonce` is omitted, the partial (nonce independent) commitment is returned
13061
- * instead : it is shared by every sub-account the user derives for this DAPP, so it can
13062
- * be published once to let a DAPP recognize all the sub-accounts of a user without
13063
- * learning any individual nonce.
13064
- * @param {STRK20_DAPP_NAME} dappName - The DAPP that scopes the sub-account(s).
13065
- * @param {FELT} [nonce] - The sub-account nonce ; each nonce selects a distinct sub-account for this user + DAPP. Omit it to get the partial commitment.
13066
- * @returns {Promise<FELT>} The sub-account commitment.
13136
+ * instead : it is shared by every shadow account the user derives for this DAPP, so it
13137
+ * can be published once to let a DAPP recognize all the shadow accounts of a user
13138
+ * without learning any individual nonce.
13139
+ * @param {STRK20_DAPP_NAME} dappName - The DAPP that scopes the shadow account(s).
13140
+ * @param {FELT} [nonce] - The shadow account nonce ; each nonce selects a distinct shadow account for this user + DAPP. Omit it to get the partial commitment.
13141
+ * @returns {Promise<FELT>} The shadow account commitment.
13067
13142
  * @example
13068
13143
  * ```typescript
13069
- * const commitment = await myWalletAccount.strk20SubaccountCommitment('myDapp', '0x0');
13144
+ * const commitment = await myWalletAccount.strk20ShadowAccountCommitment('myDapp', '0x0');
13070
13145
  * // commitment = '0x5f2e...'
13071
13146
  * ```
13072
13147
  */
13073
- strk20SubaccountCommitment(dappName, nonce) {
13074
- return strk20SubaccountCommitment(this.v6Provider, dappName, nonce);
13148
+ strk20ShadowAccountCommitment(dappName, nonce) {
13149
+ return strk20ShadowAccountCommitment(this.v6Provider, dappName, nonce);
13075
13150
  }
13076
13151
  static async connect(provider, walletProvider, cairoVersion, paymaster, silentMode = false) {
13077
13152
  const { accounts } = await standardConnect2(walletProvider, silentMode);