starknet 7.5.0 → 7.6.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.js CHANGED
@@ -48,7 +48,6 @@ __export(index_exports, {
48
48
  Contract: () => Contract,
49
49
  ContractFactory: () => ContractFactory,
50
50
  ContractInterface: () => ContractInterface,
51
- CustomError: () => CustomError,
52
51
  EDAMode: () => EDAMode,
53
52
  EDataAvailabilityMode: () => EDataAvailabilityMode,
54
53
  ETH_ADDRESS: () => ETH_ADDRESS,
@@ -85,6 +84,8 @@ __export(index_exports, {
85
84
  RpcProvider: () => RpcProvider2,
86
85
  Signer: () => Signer,
87
86
  SignerInterface: () => SignerInterface,
87
+ Subscription: () => Subscription,
88
+ TimeoutError: () => TimeoutError,
88
89
  TransactionExecutionStatus: () => TransactionExecutionStatus,
89
90
  TransactionFinalityStatus: () => TransactionFinalityStatus,
90
91
  TransactionType: () => TransactionType,
@@ -101,9 +102,9 @@ __export(index_exports, {
101
102
  UINT_512_MIN: () => UINT_512_MIN,
102
103
  Uint: () => Uint,
103
104
  ValidateType: () => ValidateType,
104
- WSSubscriptions: () => WSSubscriptions,
105
105
  WalletAccount: () => WalletAccount,
106
106
  WebSocketChannel: () => WebSocketChannel,
107
+ WebSocketNotConnectedError: () => WebSocketNotConnectedError,
107
108
  addAddressPadding: () => addAddressPadding,
108
109
  byteArray: () => byteArray_exports,
109
110
  cairo: () => cairo_exports,
@@ -117,8 +118,6 @@ __export(index_exports, {
117
118
  eth: () => eth_exports,
118
119
  events: () => events_exports,
119
120
  extractContractHashes: () => extractContractHashes,
120
- fixProto: () => fixProto,
121
- fixStack: () => fixStack,
122
121
  getCalldata: () => getCalldata,
123
122
  getChecksumAddress: () => getChecksumAddress,
124
123
  getLedgerPathBuffer: () => getLedgerPathBuffer111,
@@ -148,6 +147,7 @@ __export(index_exports, {
148
147
  stark: () => stark_exports,
149
148
  starknetId: () => starknetId_exports,
150
149
  toAnyPatchVersion: () => toAnyPatchVersion,
150
+ toApiVersion: () => toApiVersion,
151
151
  transaction: () => transaction_exports,
152
152
  typedData: () => typedData_exports,
153
153
  types: () => types_exports,
@@ -3701,6 +3701,10 @@ function toAnyPatchVersion(version) {
3701
3701
  }
3702
3702
  return `${parts[0]}.${parts[1]}.*`;
3703
3703
  }
3704
+ function toApiVersion(version) {
3705
+ const [major, minor] = version.replace(/^v/, "").split(".");
3706
+ return `v${major}_${minor}`;
3707
+ }
3704
3708
  function isPendingBlock(response) {
3705
3709
  return response.status === "PENDING";
3706
3710
  }
@@ -3985,6 +3989,18 @@ var RpcError = class extends LibraryError {
3985
3989
  return rpc_default[typeName] === this.code;
3986
3990
  }
3987
3991
  };
3992
+ var TimeoutError = class extends LibraryError {
3993
+ constructor(message) {
3994
+ super(message);
3995
+ this.name = "TimeoutError";
3996
+ }
3997
+ };
3998
+ var WebSocketNotConnectedError = class extends LibraryError {
3999
+ constructor(message) {
4000
+ super(message);
4001
+ this.name = "WebSocketNotConnectedError";
4002
+ }
4003
+ };
3988
4004
 
3989
4005
  // src/utils/eth.ts
3990
4006
  var eth_exports = {};
@@ -4017,6 +4033,7 @@ __export(provider_exports, {
4017
4033
  createSierraContractClass: () => createSierraContractClass,
4018
4034
  getDefaultNodeUrl: () => getDefaultNodeUrl,
4019
4035
  getDefaultNodes: () => getDefaultNodes,
4036
+ getSupportedRpcVersions: () => getSupportedRpcVersions,
4020
4037
  parseContract: () => parseContract,
4021
4038
  validBlockTags: () => validBlockTags,
4022
4039
  wait: () => wait
@@ -4054,15 +4071,17 @@ var getDefaultNodeUrl = (networkName, mute = false, rpcVersion) => {
4054
4071
  return nodes[randIdx];
4055
4072
  };
4056
4073
  function getDefaultNodes(rpcVersion) {
4057
- const vToUrl = (versionString) => `v${versionString.replace(/^v/, "").replace(/\./g, "_")}`;
4058
4074
  const nodes = { ...RPC_DEFAULT_NODES };
4059
4075
  Object.keys(nodes).forEach(function(key, _) {
4060
4076
  nodes[key] = nodes[key].map((it) => {
4061
- return `${it}${vToUrl(rpcVersion)}`;
4077
+ return `${it}${toApiVersion(rpcVersion)}`;
4062
4078
  });
4063
4079
  });
4064
4080
  return nodes;
4065
4081
  }
4082
+ function getSupportedRpcVersions() {
4083
+ return [...new Set(Object.values(_SupportedRpcVersion))];
4084
+ }
4066
4085
  var validBlockTags = Object.values(BlockTag);
4067
4086
  var Block = class {
4068
4087
  /**
@@ -5435,7 +5454,8 @@ var RpcChannel2 = class {
5435
5454
  type: RPCSPEC08.ETransactionType.INVOKE,
5436
5455
  sender_address: invocation.contractAddress,
5437
5456
  calldata: CallData.toHex(invocation.calldata),
5438
- version: toHex(defaultVersions.v3),
5457
+ version: toHex(invocation.version || defaultVersions.v3),
5458
+ // invocation.version as simulate can use fee and normal version
5439
5459
  ...details
5440
5460
  };
5441
5461
  }
@@ -5453,7 +5473,8 @@ var RpcChannel2 = class {
5453
5473
  },
5454
5474
  compiled_class_hash: invocation.compiledClassHash || "",
5455
5475
  sender_address: invocation.senderAddress,
5456
- version: toHex(defaultVersions.v3),
5476
+ version: toHex(invocation.version || defaultVersions.v3),
5477
+ // invocation.version as simulate can use fee and normal version
5457
5478
  ...details
5458
5479
  };
5459
5480
  }
@@ -5464,7 +5485,8 @@ var RpcChannel2 = class {
5464
5485
  constructor_calldata: CallData.toHex(invocation.constructorCalldata || []),
5465
5486
  class_hash: toHex(invocation.classHash),
5466
5487
  contract_address_salt: toHex(invocation.addressSalt || 0),
5467
- version: toHex(defaultVersions.v3),
5488
+ version: toHex(invocation.version || defaultVersions.v3),
5489
+ // invocation.version as simulate can use fee and normal version
5468
5490
  ...restDetails
5469
5491
  };
5470
5492
  }
@@ -5472,6 +5494,31 @@ var RpcChannel2 = class {
5472
5494
  }
5473
5495
  };
5474
5496
 
5497
+ // src/utils/eventEmitter.ts
5498
+ var EventEmitter = class {
5499
+ listeners = {};
5500
+ on(event, listener) {
5501
+ if (!this.listeners[event]) {
5502
+ this.listeners[event] = [];
5503
+ }
5504
+ this.listeners[event].push(listener);
5505
+ }
5506
+ off(event, listener) {
5507
+ if (!this.listeners[event]) {
5508
+ return;
5509
+ }
5510
+ this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
5511
+ }
5512
+ emit(event, data) {
5513
+ if (this.listeners[event]) {
5514
+ this.listeners[event].forEach((listener) => listener(data));
5515
+ }
5516
+ }
5517
+ clear() {
5518
+ this.listeners = {};
5519
+ }
5520
+ };
5521
+
5475
5522
  // src/utils/connect/ws.ts
5476
5523
  var ws_default = typeof WebSocket !== "undefined" && WebSocket || typeof globalThis !== "undefined" && globalThis.WebSocket || typeof window !== "undefined" && window.WebSocket.bind(window) || typeof global !== "undefined" && global.WebSocket || class {
5477
5524
  constructor() {
@@ -5481,152 +5528,184 @@ var ws_default = typeof WebSocket !== "undefined" && WebSocket || typeof globalT
5481
5528
  }
5482
5529
  };
5483
5530
 
5484
- // src/channel/ws_0_8.ts
5485
- var WSSubscriptions = {
5486
- NEW_HEADS: "newHeads",
5487
- EVENTS: "events",
5488
- TRANSACTION_STATUS: "transactionStatus",
5489
- PENDING_TRANSACTION: "pendingTransactions"
5490
- };
5491
- var WebSocketChannel = class {
5531
+ // src/channel/ws/subscription.ts
5532
+ var Subscription = class {
5492
5533
  /**
5493
- * WebSocket RPC Node URL
5494
- * @example 'wss://starknet-node.io/rpc/v0_8'
5534
+ * The containing `WebSocketChannel` instance.
5535
+ * @internal
5495
5536
  */
5496
- nodeUrl;
5497
- // public headers: object;
5498
- // readonly retries: number;
5499
- // public requestId: number;
5500
- // readonly blockIdentifier: BlockIdentifier;
5501
- // private chainId?: StarknetChainId;
5502
- // private specVersion?: string;
5503
- // private transactionRetryIntervalFallback?: number;
5504
- // readonly waitMode: Boolean; // behave like web2 rpc and return when tx is processed
5505
- // private batchClient?: BatchClient;
5506
- /**
5507
- * ws library object
5508
- */
5509
- websocket;
5537
+ channel;
5510
5538
  /**
5511
- * Assign implementation method to get 'on reorg event data'
5512
- * @example
5513
- * ```typescript
5514
- * webSocketChannel.onReorg = async function (data) {
5515
- * // ... do something when reorg happens
5516
- * }
5517
- * ```
5539
+ * The JSON-RPC method used to create this subscription.
5540
+ * @internal
5518
5541
  */
5519
- onReorg = () => {
5520
- };
5542
+ method;
5521
5543
  /**
5522
- * Assign implementation method to get 'starknet block heads'
5523
- * @example
5524
- * ```typescript
5525
- * webSocketChannel.onNewHeads = async function (data) {
5526
- * // ... do something with head data
5527
- * }
5528
- * ```
5544
+ * The parameters used to create this subscription.
5545
+ * @internal
5529
5546
  */
5530
- onNewHeads = () => {
5531
- };
5547
+ params;
5532
5548
  /**
5533
- * Assign implementation method to get 'starknet events'
5534
- * @example
5535
- * ```typescript
5536
- * webSocketChannel.onEvents = async function (data) {
5537
- * // ... do something with event data
5538
- * }
5539
- * ```
5549
+ * The unique identifier for this subscription.
5550
+ * @internal
5540
5551
  */
5541
- onEvents = () => {
5542
- };
5552
+ id;
5553
+ events = new EventEmitter();
5554
+ buffer = [];
5555
+ maxBufferSize;
5556
+ handler = null;
5557
+ _isClosed = false;
5543
5558
  /**
5544
- * Assign method to get 'starknet transactions status'
5545
- * @example
5546
- * ```typescript
5547
- * webSocketChannel.onTransactionStatus = async function (data) {
5548
- * // ... do something with tx status data
5549
- * }
5550
- * ```
5559
+ * @internal
5560
+ * @param {WebSocketChannel} channel - The WebSocketChannel instance.
5561
+ * @param {string} method - The RPC method used for the subscription.
5562
+ * @param {any} params - The parameters for the subscription.
5563
+ * @param {SUBSCRIPTION_ID} id - The subscription ID.
5564
+ * @param {number} maxBufferSize - The maximum number of events to buffer.
5551
5565
  */
5552
- onTransactionStatus = () => {
5553
- };
5566
+ constructor(channel, method, params, id, maxBufferSize) {
5567
+ this.channel = channel;
5568
+ this.method = method;
5569
+ this.params = params;
5570
+ this.id = id;
5571
+ this.maxBufferSize = maxBufferSize;
5572
+ }
5554
5573
  /**
5555
- * Assign implementation method to get 'starknet pending transactions (mempool)'
5556
- * @example
5557
- * ```typescript
5558
- * webSocketChannel.onPendingTransaction = async function (data) {
5559
- * // ... do something with pending tx data
5560
- * }
5561
- * ```
5574
+ * Indicates if the subscription has been closed.
5575
+ * @returns {boolean} `true` if unsubscribed, `false` otherwise.
5562
5576
  */
5563
- onPendingTransaction = () => {
5564
- };
5577
+ get isClosed() {
5578
+ return this._isClosed;
5579
+ }
5565
5580
  /**
5566
- * Assign implementation to this method to listen open Event
5581
+ * Internal method to handle incoming events from the WebSocket channel.
5582
+ * If a handler is attached, it's invoked immediately. Otherwise, the event is buffered.
5583
+ * @internal
5584
+ * @param {T} data - The event data.
5567
5585
  */
5568
- onOpen = () => {
5569
- };
5586
+ _handleEvent(data) {
5587
+ if (this.handler) {
5588
+ this.handler(data);
5589
+ } else {
5590
+ if (this.buffer.length >= this.maxBufferSize) {
5591
+ const droppedEvent = this.buffer.shift();
5592
+ logger.warn(`Subscription ${this.id}: Buffer full. Dropping oldest event:`, droppedEvent);
5593
+ }
5594
+ this.buffer.push(data);
5595
+ }
5596
+ }
5570
5597
  /**
5571
- * Assign implementation to this method to listen close CloseEvent
5572
- */
5573
- onClose = () => {
5574
- };
5598
+ * Attaches a handler function to be called for each event.
5599
+ *
5600
+ * When a handler is attached, any buffered events will be passed to it sequentially.
5601
+ * Subsequent events will be passed directly as they arrive.
5602
+ *
5603
+ * @param {(data: T) => void} handler - The function to call with event data.
5604
+ * @throws {Error} If a handler is already attached to this subscription.
5605
+ */
5606
+ on(handler) {
5607
+ if (this.handler) {
5608
+ throw new Error("A handler is already attached to this subscription.");
5609
+ }
5610
+ this.handler = handler;
5611
+ while (this.buffer.length > 0) {
5612
+ const event = this.buffer.shift();
5613
+ if (event) {
5614
+ this.handler(event);
5615
+ }
5616
+ }
5617
+ }
5575
5618
  /**
5576
- * Assign implementation to this method to listen message MessageEvent
5619
+ * Sends an unsubscribe request to the node and cleans up local resources.
5620
+ * @returns {Promise<boolean>} A Promise that resolves to `true` if the unsubscription was successful.
5577
5621
  */
5578
- onMessage = () => {
5579
- };
5622
+ async unsubscribe() {
5623
+ if (this._isClosed) {
5624
+ return true;
5625
+ }
5626
+ const success = await this.channel.unsubscribe(this.id);
5627
+ if (success) {
5628
+ this._isClosed = true;
5629
+ this.channel.removeSubscription(this.id);
5630
+ this.events.emit("unsubscribe", void 0);
5631
+ this.events.clear();
5632
+ }
5633
+ return success;
5634
+ }
5635
+ };
5636
+
5637
+ // src/channel/ws/ws_0_8.ts
5638
+ var WebSocketChannel = class {
5580
5639
  /**
5581
- * Assign implementation to this method to listen error Event
5640
+ * The URL of the WebSocket RPC Node.
5641
+ * @example 'wss://starknet-sepolia.public.blastapi.io/rpc/v0_8'
5582
5642
  */
5583
- onError = () => {
5584
- };
5643
+ nodeUrl;
5585
5644
  /**
5586
- * Assign implementation to this method to listen unsubscription
5645
+ * The underlying WebSocket instance.
5587
5646
  */
5588
- onUnsubscribe = () => {
5589
- };
5590
- onUnsubscribeLocal = () => {
5591
- };
5592
- /**
5593
- * JSON RPC latest sent message id
5594
- * expecting receiving message to contain same id
5647
+ websocket;
5648
+ // Store the WebSocket implementation class to allow for custom implementations.
5649
+ WsImplementation;
5650
+ // Map of active subscriptions, keyed by their ID.
5651
+ activeSubscriptions = /* @__PURE__ */ new Map();
5652
+ maxBufferSize;
5653
+ autoReconnect;
5654
+ reconnectOptions;
5655
+ requestTimeout;
5656
+ isReconnecting = false;
5657
+ reconnectAttempts = 0;
5658
+ userInitiatedClose = false;
5659
+ reconnectTimeoutId = null;
5660
+ requestQueue = [];
5661
+ events = new EventEmitter();
5662
+ openListener = (ev) => this.events.emit("open", ev);
5663
+ closeListener = this.onCloseProxy.bind(this);
5664
+ messageListener = this.onMessageProxy.bind(this);
5665
+ errorListener = (ev) => this.events.emit("error", ev);
5666
+ /**
5667
+ * JSON RPC latest sent message ID.
5668
+ * The receiving message is expected to contain the same ID.
5595
5669
  */
5596
5670
  sendId = 0;
5597
5671
  /**
5598
- * subscriptions ids
5599
- * mapped by keys WSSubscriptions
5600
- */
5601
- subscriptions = /* @__PURE__ */ new Map();
5602
- /**
5603
- * Construct class and event listeners
5604
- * @param options WebSocketOptions
5672
+ * Creates an instance of WebSocketChannel.
5673
+ * @param {WebSocketOptions} options - The options for configuring the channel.
5605
5674
  */
5606
- constructor(options = {}) {
5607
- const nodeUrl = options.nodeUrl || "http://localhost:3000 ";
5608
- this.nodeUrl = options.websocket ? options.websocket.url : nodeUrl;
5609
- this.websocket = options.websocket || config.get("websocket") || new ws_default(nodeUrl);
5610
- this.websocket.addEventListener("open", this.onOpen.bind(this));
5611
- this.websocket.addEventListener("close", this.onCloseProxy.bind(this));
5612
- this.websocket.addEventListener("message", this.onMessageProxy.bind(this));
5613
- this.websocket.addEventListener("error", this.onError.bind(this));
5675
+ constructor(options) {
5676
+ this.nodeUrl = options.nodeUrl;
5677
+ this.maxBufferSize = options.maxBufferSize ?? 1e3;
5678
+ this.autoReconnect = options.autoReconnect ?? true;
5679
+ this.reconnectOptions = {
5680
+ retries: options.reconnectOptions?.retries ?? 5,
5681
+ delay: options.reconnectOptions?.delay ?? 2e3
5682
+ };
5683
+ this.requestTimeout = options.requestTimeout ?? 6e4;
5684
+ this.WsImplementation = options.websocket || config.get("websocket") || ws_default;
5685
+ this.websocket = new this.WsImplementation(this.nodeUrl);
5686
+ this.websocket.addEventListener("open", this.openListener);
5687
+ this.websocket.addEventListener("close", this.closeListener);
5688
+ this.websocket.addEventListener("message", this.messageListener);
5689
+ this.websocket.addEventListener("error", this.errorListener);
5614
5690
  }
5615
5691
  idResolver(id) {
5616
5692
  if (id) return id;
5617
5693
  return this.sendId++;
5618
5694
  }
5619
5695
  /**
5620
- * Send data over open ws connection
5621
- * * this would only send data on the line without awaiting 'response message'
5622
- * @example
5623
- * ```typescript
5624
- * const sentId = await this.send('starknet_method', params);
5625
- * ```
5696
+ * Sends a JSON-RPC request over the WebSocket connection without waiting for a response.
5697
+ * This is a low-level method. Prefer `sendReceive` for most use cases.
5698
+ * @param {string} method - The RPC method name.
5699
+ * @param {object} [params] - The parameters for the RPC method.
5700
+ * @param {number} [id] - A specific request ID. If not provided, an auto-incrementing ID is used.
5701
+ * @returns {number} The ID of the sent request.
5702
+ * @throws {WebSocketNotConnectedError} If the WebSocket is not connected.
5626
5703
  */
5627
5704
  send(method, params, id) {
5628
5705
  if (!this.isConnected()) {
5629
- throw Error("WebSocketChannel.send() fail due to socket disconnected");
5706
+ throw new WebSocketNotConnectedError(
5707
+ "WebSocketChannel.send() failed due to socket being disconnected"
5708
+ );
5630
5709
  }
5631
5710
  const usedId = this.idResolver(id);
5632
5711
  const rpcRequestBody = {
@@ -5639,50 +5718,88 @@ var WebSocketChannel = class {
5639
5718
  return usedId;
5640
5719
  }
5641
5720
  /**
5642
- * Any Starknet method not just websocket override
5643
- */
5644
- sendReceiveAny(method, params) {
5645
- return this.sendReceive(method, params);
5646
- }
5647
- /**
5648
- * Send request and receive response over ws line
5649
- * This method abstract ws messages into request/response model
5650
- * @param method rpc method name
5651
- * @param params rpc method parameters
5652
- * @example
5653
- * ```typescript
5654
- * const response = await this.sendReceive('starknet_method', params);
5655
- * ```
5721
+ * Sends a JSON-RPC request and returns a Promise that resolves with the result.
5722
+ * This method abstracts the request/response cycle over WebSockets.
5723
+ * If the connection is lost, it will queue the request and send it upon reconnection.
5724
+ * @template T - The expected type of the result.
5725
+ * @param {string} method - The RPC method name.
5726
+ * @param {object} [params] - The parameters for the RPC method.
5727
+ * @returns {Promise<T>} A Promise that resolves with the RPC response result.
5728
+ * @throws {TimeoutError} If the request does not receive a response within the configured `requestTimeout`.
5729
+ * @throws {WebSocketNotConnectedError} If the WebSocket is not connected and auto-reconnect is disabled.
5656
5730
  */
5657
5731
  sendReceive(method, params) {
5732
+ if (this.isReconnecting || !this.isConnected() && this.autoReconnect && !this.userInitiatedClose) {
5733
+ logger.info(`WebSocket: Connection unavailable, queueing request: ${method}`);
5734
+ return new Promise((resolve, reject) => {
5735
+ this.requestQueue.push({ method, params, resolve, reject });
5736
+ });
5737
+ }
5658
5738
  const sendId = this.send(method, params);
5659
5739
  return new Promise((resolve, reject) => {
5660
- if (!this.websocket) return;
5661
- this.websocket.onmessage = ({ data }) => {
5662
- const message = JSON.parse(data);
5740
+ let timeoutId;
5741
+ if (!this.websocket || this.websocket.readyState !== ws_default.OPEN) {
5742
+ reject(new WebSocketNotConnectedError("WebSocket not available or not connected."));
5743
+ return;
5744
+ }
5745
+ const messageHandler = (event) => {
5746
+ if (!isString(event.data)) {
5747
+ logger.warn("WebSocket received non-string message data:", event.data);
5748
+ return;
5749
+ }
5750
+ const message = JSON.parse(event.data);
5663
5751
  if (message.id === sendId) {
5752
+ clearTimeout(timeoutId);
5753
+ this.websocket.removeEventListener("message", messageHandler);
5754
+ this.websocket.removeEventListener("error", errorHandler);
5664
5755
  if ("result" in message) {
5665
5756
  resolve(message.result);
5666
5757
  } else {
5667
- reject(Error(`error on ${method}, ${message.error}`));
5758
+ reject(
5759
+ new Error(`Error on ${method} (id: ${sendId}): ${JSON.stringify(message.error)}`)
5760
+ );
5668
5761
  }
5669
5762
  }
5670
5763
  };
5671
- this.websocket.onerror = reject;
5764
+ const errorHandler = (event) => {
5765
+ clearTimeout(timeoutId);
5766
+ this.websocket.removeEventListener("message", messageHandler);
5767
+ this.websocket.removeEventListener("error", errorHandler);
5768
+ reject(
5769
+ new Error(
5770
+ `WebSocket error during ${method} (id: ${sendId}): ${event.type || "Unknown error"}`
5771
+ )
5772
+ );
5773
+ };
5774
+ this.websocket.addEventListener("message", messageHandler);
5775
+ this.websocket.addEventListener("error", errorHandler);
5776
+ timeoutId = setTimeout(() => {
5777
+ this.websocket.removeEventListener("message", messageHandler);
5778
+ this.websocket.removeEventListener("error", errorHandler);
5779
+ reject(
5780
+ new TimeoutError(
5781
+ `Request ${method} (id: ${sendId}) timed out after ${this.requestTimeout}ms`
5782
+ )
5783
+ );
5784
+ }, this.requestTimeout);
5672
5785
  });
5673
5786
  }
5674
5787
  /**
5675
- * Helper to check connection is open
5788
+ * Checks if the WebSocket connection is currently open.
5789
+ * @returns {boolean} `true` if the connection is open, `false` otherwise.
5676
5790
  */
5677
5791
  isConnected() {
5678
5792
  return this.websocket.readyState === ws_default.OPEN;
5679
5793
  }
5680
5794
  /**
5681
- * await while websocket is connected
5682
- * * could be used to block the flow until websocket is open
5795
+ * Returns a Promise that resolves when the WebSocket connection is open.
5796
+ * Can be used to block execution until the connection is established.
5797
+ * @returns {Promise<number>} A Promise that resolves with the WebSocket's `readyState` when connected.
5683
5798
  * @example
5684
5799
  * ```typescript
5685
- * const readyState = await webSocketChannel.waitForConnection();
5800
+ * const channel = new WebSocketChannel({ nodeUrl: '...' });
5801
+ * await channel.waitForConnection();
5802
+ * console.log('Connected!');
5686
5803
  * ```
5687
5804
  */
5688
5805
  async waitForConnection() {
@@ -5698,17 +5815,22 @@ var WebSocketChannel = class {
5698
5815
  return this.websocket.readyState;
5699
5816
  }
5700
5817
  /**
5701
- * Disconnect the WebSocket connection, optionally using code as the the WebSocket connection close code and reason as the the WebSocket connection close reason.
5818
+ * Closes the WebSocket connection.
5819
+ * This method is user-initiated and will prevent automatic reconnection for this closure.
5820
+ * @param {number} [code] - The WebSocket connection close code.
5821
+ * @param {string} [reason] - The WebSocket connection close reason.
5702
5822
  */
5703
5823
  disconnect(code, reason) {
5824
+ if (this.reconnectTimeoutId) {
5825
+ clearTimeout(this.reconnectTimeoutId);
5826
+ this.reconnectTimeoutId = null;
5827
+ }
5704
5828
  this.websocket.close(code, reason);
5829
+ this.userInitiatedClose = true;
5705
5830
  }
5706
5831
  /**
5707
- * await while websocket is disconnected
5708
- * @example
5709
- * ```typescript
5710
- * const readyState = await webSocketChannel.waitForDisconnection();
5711
- * ```
5832
+ * Returns a Promise that resolves when the WebSocket connection is closed.
5833
+ * @returns {Promise<number | Event>} A Promise that resolves with the WebSocket's `readyState` or a `CloseEvent` when disconnected.
5712
5834
  */
5713
5835
  async waitForDisconnection() {
5714
5836
  if (this.websocket.readyState !== ws_default.CLOSED) {
@@ -5721,206 +5843,233 @@ var WebSocketChannel = class {
5721
5843
  return this.websocket.readyState;
5722
5844
  }
5723
5845
  /**
5724
- * Unsubscribe from starknet subscription
5725
- * @param subscriptionId
5726
- * @param ref internal usage, only for managed subscriptions
5846
+ * Unsubscribes from a Starknet subscription.
5847
+ * It is recommended to use the `unsubscribe()` method on the `Subscription` object instead.
5848
+ * @internal
5849
+ * @param {SUBSCRIPTION_ID} subscriptionId - The ID of the subscription to unsubscribe from.
5850
+ * @returns {Promise<boolean>} A Promise that resolves with `true` if the unsubscription was successful.
5727
5851
  */
5728
- async unsubscribe(subscriptionId, ref) {
5852
+ async unsubscribe(subscriptionId) {
5729
5853
  const status = await this.sendReceive("starknet_unsubscribe", {
5730
5854
  subscription_id: subscriptionId
5731
5855
  });
5732
5856
  if (status) {
5733
- if (ref) {
5734
- this.subscriptions.delete(ref);
5735
- }
5736
- this.onUnsubscribeLocal(subscriptionId);
5737
- this.onUnsubscribe(subscriptionId);
5857
+ this.events.emit("unsubscribe", subscriptionId);
5738
5858
  }
5739
5859
  return status;
5740
5860
  }
5741
5861
  /**
5742
- * await while subscription is unsubscribed
5743
- * @param forSubscriptionId if defined trigger on subscriptionId else trigger on any
5744
- * @returns subscriptionId | onerror(Event)
5862
+ * Returns a Promise that resolves when a specific subscription is successfully unsubscribed.
5863
+ * @param {SUBSCRIPTION_ID} targetId - The ID of the subscription to wait for.
5864
+ * @returns {Promise<void>}
5745
5865
  * @example
5746
5866
  * ```typescript
5747
- * const subscriptionId = await webSocketChannel.waitForUnsubscription();
5867
+ * await channel.waitForUnsubscription(mySubscription.id);
5868
+ * console.log('Successfully unsubscribed.');
5748
5869
  * ```
5749
5870
  */
5750
- async waitForUnsubscription(forSubscriptionId) {
5751
- return new Promise((resolve, reject) => {
5752
- if (!this.websocket) return;
5753
- this.onUnsubscribeLocal = (subscriptionId) => {
5754
- if (forSubscriptionId === void 0) {
5755
- resolve(subscriptionId);
5756
- } else if (subscriptionId === forSubscriptionId) {
5757
- resolve(subscriptionId);
5871
+ waitForUnsubscription(targetId) {
5872
+ return new Promise((resolve) => {
5873
+ const listener = (unsubId) => {
5874
+ if (unsubId === targetId) {
5875
+ this.events.off("unsubscribe", listener);
5876
+ resolve();
5758
5877
  }
5759
5878
  };
5760
- this.websocket.onerror = reject;
5879
+ this.events.on("unsubscribe", listener);
5761
5880
  });
5762
5881
  }
5763
5882
  /**
5764
- * Reconnect re-create this.websocket instance
5883
+ * Manually initiates a reconnection attempt.
5884
+ * This creates a new WebSocket instance and re-establishes listeners.
5765
5885
  */
5766
5886
  reconnect() {
5767
- this.websocket = new ws_default(this.nodeUrl);
5768
- this.websocket.addEventListener("open", this.onOpen.bind(this));
5769
- this.websocket.addEventListener("close", this.onCloseProxy.bind(this));
5770
- this.websocket.addEventListener("message", this.onMessageProxy.bind(this));
5771
- this.websocket.addEventListener("error", this.onError.bind(this));
5887
+ this.userInitiatedClose = false;
5888
+ this.websocket = new this.WsImplementation(this.nodeUrl);
5889
+ this.websocket.addEventListener("open", this.openListener);
5890
+ this.websocket.addEventListener("close", this.closeListener);
5891
+ this.websocket.addEventListener("message", this.messageListener);
5892
+ this.websocket.addEventListener("error", this.errorListener);
5893
+ }
5894
+ _processRequestQueue() {
5895
+ logger.info(`WebSocket: Processing ${this.requestQueue.length} queued requests.`);
5896
+ while (this.requestQueue.length > 0) {
5897
+ const { method, params, resolve, reject } = this.requestQueue.shift();
5898
+ this.sendReceive(method, params).then(resolve).catch(reject);
5899
+ }
5900
+ }
5901
+ async _restoreSubscriptions() {
5902
+ const oldSubscriptions = Array.from(this.activeSubscriptions.values());
5903
+ this.activeSubscriptions.clear();
5904
+ const restorePromises = oldSubscriptions.map(async (sub) => {
5905
+ try {
5906
+ const newSubId = await this.sendReceive(sub.method, sub.params);
5907
+ sub.id = newSubId;
5908
+ this.activeSubscriptions.set(newSubId, sub);
5909
+ logger.info(`Subscription ${sub.method} restored with new ID: ${newSubId}`);
5910
+ } catch (error) {
5911
+ logger.error(`Failed to restore subscription ${sub.method}:`, error);
5912
+ }
5913
+ });
5914
+ await Promise.all(restorePromises);
5772
5915
  }
5773
- // TODO: Add/Test ping service. It seems this work out of the box from pathfinder. If net disc. it will auto replay.
5774
- reconnectAndUpdate() {
5775
- this.reconnect();
5916
+ _startReconnect() {
5917
+ if (this.isReconnecting || !this.autoReconnect) {
5918
+ return;
5919
+ }
5920
+ this.isReconnecting = true;
5921
+ this.reconnectAttempts = 0;
5922
+ const tryReconnect = () => {
5923
+ if (this.reconnectAttempts >= this.reconnectOptions.retries) {
5924
+ logger.error("WebSocket: Maximum reconnection retries reached. Giving up.");
5925
+ this.isReconnecting = false;
5926
+ return;
5927
+ }
5928
+ this.reconnectAttempts += 1;
5929
+ logger.info(
5930
+ `WebSocket: Connection lost. Attempting to reconnect... (${this.reconnectAttempts}/${this.reconnectOptions.retries})`
5931
+ );
5932
+ this.reconnect();
5933
+ this.websocket.onopen = async () => {
5934
+ logger.info("WebSocket: Reconnection successful.");
5935
+ this.isReconnecting = false;
5936
+ this.reconnectAttempts = 0;
5937
+ await this._restoreSubscriptions();
5938
+ this._processRequestQueue();
5939
+ this.events.emit("open", new Event("open"));
5940
+ };
5941
+ this.websocket.onerror = () => {
5942
+ const delay = this.reconnectOptions.delay * 2 ** (this.reconnectAttempts - 1);
5943
+ logger.info(`WebSocket: Reconnect attempt failed. Retrying in ${delay}ms.`);
5944
+ this.reconnectTimeoutId = setTimeout(tryReconnect, delay);
5945
+ };
5946
+ };
5947
+ tryReconnect();
5776
5948
  }
5777
5949
  onCloseProxy(ev) {
5778
- this.websocket.removeEventListener("open", this.onOpen);
5779
- this.websocket.removeEventListener("close", this.onCloseProxy);
5780
- this.websocket.removeEventListener("message", this.onMessageProxy);
5781
- this.websocket.removeEventListener("error", this.onError);
5782
- this.onClose(ev);
5950
+ this.websocket.removeEventListener("open", this.openListener);
5951
+ this.websocket.removeEventListener("close", this.closeListener);
5952
+ this.websocket.removeEventListener("message", this.messageListener);
5953
+ this.websocket.removeEventListener("error", this.errorListener);
5954
+ this.events.emit("close", ev);
5955
+ if (!this.userInitiatedClose) {
5956
+ this._startReconnect();
5957
+ }
5783
5958
  }
5784
5959
  onMessageProxy(event) {
5785
- const message = JSON.parse(event.data);
5786
- const eventName = message.method;
5787
- switch (eventName) {
5788
- case "starknet_subscriptionReorg":
5789
- this.onReorg(message.params);
5790
- break;
5791
- case "starknet_subscriptionNewHeads":
5792
- this.onNewHeads(message.params);
5793
- break;
5794
- case "starknet_subscriptionEvents":
5795
- this.onEvents(message.params);
5796
- break;
5797
- case "starknet_subscriptionTransactionStatus":
5798
- this.onTransactionStatus(message.params);
5799
- break;
5800
- case "starknet_subscriptionPendingTransactions":
5801
- this.onPendingTransaction(message.params);
5802
- break;
5803
- default:
5804
- break;
5960
+ let message;
5961
+ try {
5962
+ message = JSON.parse(event.data);
5963
+ } catch (error) {
5964
+ logger.error(
5965
+ `WebSocketChannel: Error parsing incoming message: ${event.data}, Error: ${error}`
5966
+ );
5967
+ return;
5805
5968
  }
5806
- this.onMessage(event);
5807
- }
5808
- /**
5809
- * subscribe to new block heads
5810
- * * you can subscribe to this event multiple times and you need to manage subscriptions manually
5811
- */
5812
- subscribeNewHeadsUnmanaged(blockIdentifier) {
5813
- const block_id = blockIdentifier ? new Block(blockIdentifier).identifier : void 0;
5814
- return this.sendReceive("starknet_subscribeNewHeads", {
5815
- ...{ block_id }
5816
- });
5969
+ if (message.method && isObject(message.params) && "subscription_id" in message.params) {
5970
+ const { result, subscription_id } = message.params;
5971
+ const subscription = this.activeSubscriptions.get(subscription_id);
5972
+ if (subscription) {
5973
+ subscription._handleEvent(result);
5974
+ } else {
5975
+ logger.warn(
5976
+ `WebSocketChannel: Received event for untracked subscription ID: ${subscription_id}.`
5977
+ );
5978
+ }
5979
+ }
5980
+ logger.debug("onMessageProxy:", event.data);
5981
+ this.events.emit("message", event);
5817
5982
  }
5818
5983
  /**
5819
- * subscribe to new block heads
5984
+ * Subscribes to new block headers.
5985
+ * @param {SubscriptionBlockIdentifier} [blockIdentifier] - The block to start receiving notifications from. Defaults to 'latest'.
5986
+ * @returns {Promise<Subscription<BLOCK_HEADER>>} A Promise that resolves with a `Subscription` object for new block headers.
5820
5987
  */
5821
5988
  async subscribeNewHeads(blockIdentifier) {
5822
- if (this.subscriptions.get(WSSubscriptions.NEW_HEADS)) return false;
5823
- const subId = await this.subscribeNewHeadsUnmanaged(blockIdentifier);
5824
- this.subscriptions.set(WSSubscriptions.NEW_HEADS, subId);
5825
- return subId;
5826
- }
5827
- /**
5828
- * Unsubscribe newHeads subscription
5829
- */
5830
- async unsubscribeNewHeads() {
5831
- const subId = this.subscriptions.get(WSSubscriptions.NEW_HEADS);
5832
- if (!subId) throw Error("There is no subscription on this event");
5833
- return this.unsubscribe(subId, WSSubscriptions.NEW_HEADS);
5834
- }
5835
- /**
5836
- * subscribe to 'starknet events'
5837
- * * you can subscribe to this event multiple times and you need to manage subscriptions manually
5838
- */
5839
- subscribeEventsUnmanaged(fromAddress, keys, blockIdentifier) {
5840
- const block_id = blockIdentifier ? new Block(blockIdentifier).identifier : void 0;
5841
- return this.sendReceive("starknet_subscribeEvents", {
5842
- ...{ from_address: fromAddress !== void 0 ? toHex(fromAddress) : void 0 },
5843
- ...{ keys },
5844
- ...{ block_id }
5845
- });
5989
+ const method = "starknet_subscribeNewHeads";
5990
+ const params = {
5991
+ block_id: blockIdentifier ? new Block(blockIdentifier).identifier : void 0
5992
+ };
5993
+ const subId = await this.sendReceive(method, params);
5994
+ const subscription = new Subscription(this, method, params, subId, this.maxBufferSize);
5995
+ this.activeSubscriptions.set(subId, subscription);
5996
+ return subscription;
5846
5997
  }
5847
5998
  /**
5848
- * subscribe to 'starknet events'
5999
+ * Subscribes to events matching a given filter.
6000
+ * @param {BigNumberish} [fromAddress] - The contract address to filter by.
6001
+ * @param {string[][]} [keys] - The event keys to filter by.
6002
+ * @param {SubscriptionBlockIdentifier} [blockIdentifier] - The block to start receiving notifications from. Defaults to 'latest'.
6003
+ * @returns {Promise<Subscription<EMITTED_EVENT>>} A Promise that resolves with a `Subscription` object for the specified events.
5849
6004
  */
5850
6005
  async subscribeEvents(fromAddress, keys, blockIdentifier) {
5851
- if (this.subscriptions.get(WSSubscriptions.EVENTS)) return false;
5852
- const subId = await this.subscribeEventsUnmanaged(fromAddress, keys, blockIdentifier);
5853
- this.subscriptions.set(WSSubscriptions.EVENTS, subId);
5854
- return subId;
5855
- }
5856
- /**
5857
- * Unsubscribe 'starknet events' subscription
5858
- */
5859
- unsubscribeEvents() {
5860
- const subId = this.subscriptions.get(WSSubscriptions.EVENTS);
5861
- if (!subId) throw Error("There is no subscription ID for this event");
5862
- return this.unsubscribe(subId, WSSubscriptions.EVENTS);
5863
- }
5864
- /**
5865
- * subscribe to transaction status
5866
- * * you can subscribe to this event multiple times and you need to manage subscriptions manually
5867
- */
5868
- subscribeTransactionStatusUnmanaged(transactionHash, blockIdentifier) {
5869
- const transaction_hash = toHex(transactionHash);
5870
- const block_id = blockIdentifier ? new Block(blockIdentifier).identifier : void 0;
5871
- return this.sendReceive("starknet_subscribeTransactionStatus", {
5872
- transaction_hash,
5873
- ...{ block_id }
5874
- });
6006
+ const method = "starknet_subscribeEvents";
6007
+ const params = {
6008
+ from_address: fromAddress !== void 0 ? toHex(fromAddress) : void 0,
6009
+ keys,
6010
+ block_id: blockIdentifier ? new Block(blockIdentifier).identifier : void 0
6011
+ };
6012
+ const subId = await this.sendReceive(method, params);
6013
+ const subscription = new Subscription(this, method, params, subId, this.maxBufferSize);
6014
+ this.activeSubscriptions.set(subId, subscription);
6015
+ return subscription;
5875
6016
  }
5876
6017
  /**
5877
- * subscribe to transaction status
6018
+ * Subscribes to status updates for a specific transaction.
6019
+ * @param {BigNumberish} transactionHash - The hash of the transaction to monitor.
6020
+ * @param {SubscriptionBlockIdentifier} [blockIdentifier] - The block context. Not typically required.
6021
+ * @returns {Promise<Subscription<NEW_TXN_STATUS>>} A Promise that resolves with a `Subscription` object for the transaction's status.
5878
6022
  */
5879
- async subscribeTransactionStatus(transactionHash) {
5880
- if (this.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS)) return false;
5881
- const subId = await this.subscribeTransactionStatusUnmanaged(transactionHash);
5882
- this.subscriptions.set(WSSubscriptions.TRANSACTION_STATUS, subId);
5883
- return subId;
6023
+ async subscribeTransactionStatus(transactionHash, blockIdentifier) {
6024
+ const method = "starknet_subscribeTransactionStatus";
6025
+ const params = {
6026
+ transaction_hash: toHex(transactionHash),
6027
+ block_id: blockIdentifier ? new Block(blockIdentifier).identifier : void 0
6028
+ };
6029
+ const subId = await this.sendReceive(method, params);
6030
+ const subscription = new Subscription(this, method, params, subId, this.maxBufferSize);
6031
+ this.activeSubscriptions.set(subId, subscription);
6032
+ return subscription;
5884
6033
  }
5885
6034
  /**
5886
- * unsubscribe 'transaction status' subscription
6035
+ * Subscribes to pending transactions.
6036
+ * @param {boolean} [transactionDetails] - If `true`, the full transaction details are included. Defaults to `false` (hash only).
6037
+ * @param {BigNumberish[]} [senderAddress] - An array of sender addresses to filter by.
6038
+ * @returns {Promise<Subscription<TXN_HASH | TXN_WITH_HASH>>} A Promise that resolves with a `Subscription` object for pending transactions.
5887
6039
  */
5888
- async unsubscribeTransactionStatus() {
5889
- const subId = this.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS);
5890
- if (!subId) throw Error("There is no subscription ID for this event");
5891
- return this.unsubscribe(subId, WSSubscriptions.TRANSACTION_STATUS);
6040
+ async subscribePendingTransaction(transactionDetails, senderAddress) {
6041
+ const method = "starknet_subscribePendingTransactions";
6042
+ const params = {
6043
+ transaction_details: transactionDetails,
6044
+ sender_address: senderAddress && bigNumberishArrayToHexadecimalStringArray(senderAddress)
6045
+ };
6046
+ const subId = await this.sendReceive(method, params);
6047
+ const subscription = new Subscription(this, method, params, subId, this.maxBufferSize);
6048
+ this.activeSubscriptions.set(subId, subscription);
6049
+ return subscription;
5892
6050
  }
5893
6051
  /**
5894
- * subscribe to pending transactions (mempool)
5895
- * * you can subscribe to this event multiple times and you need to manage subscriptions manually
6052
+ * Internal method to remove subscription from active map.
6053
+ * @internal
5896
6054
  */
5897
- subscribePendingTransactionUnmanaged(transactionDetails, senderAddress) {
5898
- return this.sendReceive("starknet_subscribePendingTransactions", {
5899
- ...{ transaction_details: transactionDetails },
5900
- ...{
5901
- sender_address: senderAddress && bigNumberishArrayToHexadecimalStringArray(senderAddress)
5902
- }
5903
- });
6055
+ removeSubscription(id) {
6056
+ this.activeSubscriptions.delete(id);
5904
6057
  }
5905
6058
  /**
5906
- * subscribe to pending transactions (mempool)
6059
+ * Adds a listener for a given event.
6060
+ * @param event The event name.
6061
+ * @param listener The listener function to add.
5907
6062
  */
5908
- async subscribePendingTransaction(transactionDetails, senderAddress) {
5909
- if (this.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS)) return false;
5910
- const subId = await this.subscribePendingTransactionUnmanaged(
5911
- transactionDetails,
5912
- senderAddress
5913
- );
5914
- this.subscriptions.set(WSSubscriptions.PENDING_TRANSACTION, subId);
5915
- return subId;
6063
+ on(event, listener) {
6064
+ this.events.on(event, listener);
5916
6065
  }
5917
6066
  /**
5918
- * unsubscribe 'pending transaction' subscription
6067
+ * Removes a listener for a given event.
6068
+ * @param event The event name.
6069
+ * @param listener The listener function to remove.
5919
6070
  */
5920
- async unsubscribePendingTransaction() {
5921
- const subId = this.subscriptions.get(WSSubscriptions.PENDING_TRANSACTION);
5922
- if (!subId) throw Error("There is no subscription ID for this event");
5923
- return this.unsubscribe(subId, WSSubscriptions.PENDING_TRANSACTION);
6071
+ off(event, listener) {
6072
+ this.events.off(event, listener);
5924
6073
  }
5925
6074
  };
5926
6075
 
@@ -10788,7 +10937,6 @@ function units(amount, simbol = "fri") {
10788
10937
  Contract,
10789
10938
  ContractFactory,
10790
10939
  ContractInterface,
10791
- CustomError,
10792
10940
  EDAMode,
10793
10941
  EDataAvailabilityMode,
10794
10942
  ETH_ADDRESS,
@@ -10825,6 +10973,8 @@ function units(amount, simbol = "fri") {
10825
10973
  RpcProvider,
10826
10974
  Signer,
10827
10975
  SignerInterface,
10976
+ Subscription,
10977
+ TimeoutError,
10828
10978
  TransactionExecutionStatus,
10829
10979
  TransactionFinalityStatus,
10830
10980
  TransactionType,
@@ -10841,9 +10991,9 @@ function units(amount, simbol = "fri") {
10841
10991
  UINT_512_MIN,
10842
10992
  Uint,
10843
10993
  ValidateType,
10844
- WSSubscriptions,
10845
10994
  WalletAccount,
10846
10995
  WebSocketChannel,
10996
+ WebSocketNotConnectedError,
10847
10997
  addAddressPadding,
10848
10998
  byteArray,
10849
10999
  cairo,
@@ -10857,8 +11007,6 @@ function units(amount, simbol = "fri") {
10857
11007
  eth,
10858
11008
  events,
10859
11009
  extractContractHashes,
10860
- fixProto,
10861
- fixStack,
10862
11010
  getCalldata,
10863
11011
  getChecksumAddress,
10864
11012
  getLedgerPathBuffer,
@@ -10888,6 +11036,7 @@ function units(amount, simbol = "fri") {
10888
11036
  stark,
10889
11037
  starknetId,
10890
11038
  toAnyPatchVersion,
11039
+ toApiVersion,
10891
11040
  transaction,
10892
11041
  typedData,
10893
11042
  types,