@talismn/chain-connectors 0.1.1 → 1.0.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
@@ -1,1178 +1,616 @@
1
- // src/dot/ChainConnectorDot.ts
2
- import { Deferred, isTruthy, sleep, throwAfter } from "@talismn/util";
3
-
4
- // src/log.ts
1
+ import { createClient } from "@polkadot-api/substrate-client";
2
+ import { WsEvent, getWsProvider } from "@polkadot-api/ws-provider";
5
3
  import anylogger from "anylogger";
6
-
7
- // package.json
8
- var package_default = {
9
- name: "@talismn/chain-connectors",
10
- version: "0.1.1",
11
- author: "Talisman",
12
- homepage: "https://talisman.xyz",
13
- license: "GPL-3.0-or-later",
14
- publishConfig: {
15
- access: "public"
16
- },
17
- repository: {
18
- directory: "packages/chain-connectors",
19
- type: "git",
20
- url: "https://github.com/TalismanSociety/talisman.git"
21
- },
22
- main: "./dist/index.js",
23
- module: "./dist/index.mjs",
24
- files: [
25
- "dist"
26
- ],
27
- engines: {
28
- node: ">=20"
29
- },
30
- scripts: {
31
- test: "vitest run",
32
- clean: "rm -rf dist .turbo node_modules",
33
- build: "tsup --silent",
34
- typecheck: "tsc --noEmit"
35
- },
36
- dependencies: {
37
- "@solana/web3.js": "^1.98.2",
38
- "@talismn/chaindata-provider": "workspace:*",
39
- "@talismn/connection-meta": "workspace:*",
40
- "@talismn/util": "workspace:*",
41
- anylogger: "^1.0.11",
42
- eventemitter3: "^5.0.0",
43
- "lodash-es": "4.18.1",
44
- viem: "2.52.2"
45
- },
46
- devDependencies: {
47
- "@polkadot/rpc-provider": "16.1.2",
48
- "@polkadot/x-global": "13.5.3",
49
- "@polkadot/x-ws": "13.5.3",
50
- "@talismn/tsconfig": "workspace:*",
51
- typescript: "^6.0.3"
52
- },
53
- peerDependencies: {
54
- "@polkadot/rpc-provider": "*",
55
- "@polkadot/x-global": "*",
56
- "@polkadot/x-ws": "*"
57
- },
58
- types: "./dist/index.d.ts",
59
- exports: {
60
- ".": {
61
- "@talismn/source": "./src/index.ts",
62
- import: {
63
- types: "./dist/index.d.mts",
64
- default: "./dist/index.mjs"
65
- },
66
- require: {
67
- types: "./dist/index.d.ts",
68
- default: "./dist/index.js"
69
- }
70
- }
71
- }
72
- };
73
-
74
- // src/log.ts
75
- var log_default = anylogger(package_default.name);
76
-
77
- // src/dot/Websocket.ts
78
- import { RpcCoder } from "@polkadot/rpc-provider/coder";
79
- import { getWSErrorString } from "@polkadot/rpc-provider/ws/errors";
80
- import { xglobal } from "@polkadot/x-global";
81
- import { WebSocket } from "@polkadot/x-ws";
82
- import EventEmitter from "eventemitter3";
83
-
84
- // src/dot/helpers.ts
85
- var twoSecondsMs = 2 * 1e3;
86
- var twoMinutesMs = 2 * 60 * 1e3;
87
- var ExponentialBackoff = class {
88
- #minInterval;
89
- #maxInterval;
90
- #nextInterval = 0;
91
- #active = true;
92
- constructor(maxIntervalMs = twoMinutesMs, minIntervalMs = twoSecondsMs) {
93
- this.#minInterval = minIntervalMs;
94
- this.#maxInterval = maxIntervalMs;
95
- this.reset();
96
- }
97
- enable() {
98
- this.#active = true;
99
- }
100
- disable() {
101
- this.#active = false;
102
- }
103
- increase() {
104
- if (this.#nextInterval === 0) this.#nextInterval = 1;
105
- this.#nextInterval = this.#capMax(this.#capMin(this.#nextInterval * 2));
106
- }
107
- decrease() {
108
- this.#nextInterval = this.#capMax(this.#capMin(this.#nextInterval / 2));
109
- }
110
- reset() {
111
- this.#nextInterval = this.#minInterval;
112
- }
113
- resetTo(nextInterval) {
114
- this.#nextInterval = this.#capMax(this.#capMin(nextInterval));
115
- }
116
- resetToMax() {
117
- this.#nextInterval = this.#maxInterval;
118
- }
119
- get isActive() {
120
- return this.#active;
121
- }
122
- get next() {
123
- return this.#nextInterval;
124
- }
125
- get isMin() {
126
- return this.#nextInterval === this.#minInterval;
127
- }
128
- get isMax() {
129
- return this.#nextInterval === this.#maxInterval;
130
- }
131
- #capMin(value) {
132
- return Math.max(this.#minInterval, value);
133
- }
134
- #capMax(value) {
135
- return Math.min(this.#maxInterval, value);
136
- }
137
- };
138
-
139
- // src/dot/Websocket.ts
140
- var isChildClass = (Parent, Child) => Child != null && (Child === Parent || Child.prototype instanceof Parent);
141
- var ALIASES = {
142
- chain_finalisedHead: "chain_finalizedHead",
143
- chain_subscribeFinalisedHeads: "chain_subscribeFinalizedHeads",
144
- chain_unsubscribeFinalisedHeads: "chain_unsubscribeFinalizedHeads"
145
- };
146
- var DEFAULT_TIMEOUT_MS = 60 * 1e3;
147
- var TIMEOUT_INTERVAL = 5e3;
148
- function eraseRecord(record, cb) {
149
- Object.keys(record).forEach((key) => {
150
- if (cb) {
151
- cb(record[key]);
152
- }
153
- delete record[key];
154
- });
155
- }
156
- var Websocket = class _Websocket {
157
- #coder;
158
- #endpoints;
159
- #headers;
160
- #eventemitter;
161
- #handlers = {};
162
- #isReadyPromise;
163
- #waitingForId = {};
164
- #autoConnectBackoff;
165
- #endpointIndex;
166
- #endpointsTriedSinceLastConnection = 0;
167
- #isConnected = false;
168
- #subscriptions = {};
169
- #timeoutId = null;
170
- #websocket;
171
- #timeout;
172
- /**
173
- * @param {string | string[]} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944`, may provide an array of endpoint strings.
174
- * @param {Record<string, string>} headers The headers provided to the underlying WebSocket
175
- * @param {number} [timeout] Custom timeout value used per request . Defaults to `DEFAULT_TIMEOUT_MS`
176
- */
177
- constructor(endpoint, headers = {}, timeout, nextBackoffInterval) {
178
- const endpoints = Array.isArray(endpoint) ? endpoint : [endpoint];
179
- if (endpoints.length === 0) {
180
- throw new Error("Websocket requires at least one Endpoint");
181
- }
182
- endpoints.forEach((endpoint2) => {
183
- if (!/^(wss|ws):\/\//.test(endpoint2)) {
184
- throw new Error(`Endpoint should start with 'ws://', received '${endpoint2}'`);
185
- }
186
- });
187
- this.#eventemitter = new EventEmitter();
188
- this.#autoConnectBackoff = new ExponentialBackoff();
189
- if (nextBackoffInterval) this.#autoConnectBackoff.resetTo(nextBackoffInterval);
190
- this.#coder = new RpcCoder();
191
- this.#endpointIndex = -1;
192
- this.#endpoints = endpoints;
193
- this.#headers = headers;
194
- this.#websocket = null;
195
- this.#timeout = timeout || DEFAULT_TIMEOUT_MS;
196
- if (this.#autoConnectBackoff.isActive) {
197
- this.connectWithRetry().catch(() => {
198
- });
199
- }
200
- this.#isReadyPromise = new Promise((resolve) => {
201
- this.#eventemitter.once("connected", () => {
202
- resolve(this);
203
- });
204
- });
205
- }
206
- /**
207
- * @summary `true` when this provider supports subscriptions
208
- */
209
- get hasSubscriptions() {
210
- return true;
211
- }
212
- /**
213
- * @summary `true` when this provider supports clone()
214
- */
215
- get isClonable() {
216
- return true;
217
- }
218
- /**
219
- * @summary Whether the node is connected or not.
220
- * @return {boolean} true if connected
221
- */
222
- get isConnected() {
223
- return this.#isConnected;
224
- }
225
- /**
226
- * @description Promise that resolves the first time we are connected and loaded
227
- */
228
- get isReady() {
229
- return this.#isReadyPromise;
230
- }
231
- get endpoint() {
232
- return this.#endpoints[this.#endpointIndex];
233
- }
234
- /**
235
- * @description Returns a clone of the object
236
- */
237
- clone() {
238
- return new _Websocket(this.#endpoints);
239
- }
240
- selectEndpointIndex(endpoints) {
241
- this.#endpointsTriedSinceLastConnection += 1;
242
- return (this.#endpointIndex + 1) % endpoints.length;
243
- }
244
- /**
245
- * @summary Manually connect
246
- * @description The [[Websocket]] connects automatically by default, however if you decided otherwise, you may
247
- * connect manually using this method.
248
- */
249
- async connect() {
250
- if (this.#websocket) {
251
- throw new Error("WebSocket is already connected");
252
- }
253
- try {
254
- this.#endpointIndex = this.selectEndpointIndex(this.#endpoints);
255
- this.#websocket = typeof xglobal.WebSocket !== "undefined" && isChildClass(xglobal.WebSocket, WebSocket) ? new WebSocket(this.endpoint) : (
256
- // @ts-expect-error - WS may be an instance of ws, which supports options
257
- new WebSocket(this.endpoint, void 0, {
258
- headers: this.#headers
259
- })
260
- );
261
- if (this.#websocket) {
262
- this.#websocket.onclose = this.#onSocketClose;
263
- this.#websocket.onerror = this.#onSocketError;
264
- this.#websocket.onmessage = this.#onSocketMessage;
265
- this.#websocket.onopen = this.#onSocketOpen;
266
- }
267
- this.#timeoutId = setInterval(() => this.#timeoutHandlers(), TIMEOUT_INTERVAL);
268
- } catch (error) {
269
- log_default.error(error);
270
- this.#emit("error", error);
271
- throw error;
272
- }
273
- }
274
- /**
275
- * @description Connect, never throwing an error, but rather forcing a retry
276
- */
277
- async connectWithRetry() {
278
- if (!this.#autoConnectBackoff.isActive) return;
279
- try {
280
- await this.connect();
281
- } catch {
282
- this.scheduleNextRetry();
283
- }
284
- }
285
- scheduleNextRetry() {
286
- if (!this.#autoConnectBackoff.isActive) return;
287
- const haveTriedAllEndpoints = this.#endpointsTriedSinceLastConnection > 0 && this.#endpointsTriedSinceLastConnection % this.#endpoints.length === 0;
288
- setTimeout(
289
- () => {
290
- this.connectWithRetry().catch(() => {
291
- });
292
- },
293
- haveTriedAllEndpoints ? this.#autoConnectBackoff.next : 0
294
- );
295
- if (haveTriedAllEndpoints) this.#autoConnectBackoff.increase();
296
- if (haveTriedAllEndpoints)
297
- this.#emit("stale-rpcs", { nextBackoffInterval: this.#autoConnectBackoff.next });
298
- }
299
- /**
300
- * @description Manually disconnect from the connection, clearing auto-connect logic
301
- */
302
- async disconnect() {
303
- this.#autoConnectBackoff.disable();
304
- try {
305
- if (this.#websocket) {
306
- this.#websocket.close(1e3);
307
- }
308
- } catch (error) {
309
- log_default.error(error);
310
- this.#emit("error", error);
311
- throw error;
312
- }
313
- }
314
- /**
315
- * @summary Listens on events after having subscribed using the [[subscribe]] function.
316
- * @param {ProviderInterfaceEmitted} type Event
317
- * @param {ProviderInterfaceEmitCb} sub Callback
318
- * @return unsubscribe function
319
- */
320
- on(type, sub) {
321
- this.#eventemitter.on(type, sub);
322
- return () => {
323
- this.#eventemitter.removeListener(type, sub);
324
- };
325
- }
326
- /**
327
- * @summary Send JSON data using WebSockets to configured HTTP Endpoint or queue.
328
- * @param method The RPC methods to execute
329
- * @param params Encoded parameters as applicable for the method
330
- * @param subscription Subscription details (internally used)
331
- */
332
- // biome-ignore lint/suspicious/noExplicitAny: legacy
333
- send(method, params, _isCacheable, subscription) {
334
- const [id, body] = this.#coder.encodeJson(method, params);
335
- const resultPromise = this.#send(id, body, method, params, subscription);
336
- return resultPromise;
337
- }
338
- async #send(id, body, method, params, subscription) {
339
- return new Promise((resolve, reject) => {
340
- try {
341
- if (!this.isConnected || this.#websocket === null) {
342
- throw new Error("WebSocket is not connected");
343
- }
344
- const callback = (error, result) => {
345
- error ? reject(error) : resolve(result);
346
- };
347
- this.#handlers[id] = {
348
- callback,
349
- method,
350
- params,
351
- start: Date.now(),
352
- subscription
353
- };
354
- this.#websocket.send(body);
355
- } catch (error) {
356
- reject(error);
357
- }
358
- });
359
- }
360
- /**
361
- * @name subscribe
362
- * @summary Allows subscribing to a specific event.
363
- *
364
- * @example
365
- * <BR>
366
- *
367
- * ```javascript
368
- * const provider = new Websocket('ws://127.0.0.1:9944');
369
- * const rpc = new Rpc(provider);
370
- *
371
- * rpc.state.subscribeStorage([[storage.system.account, <Address>]], (_, values) => {
372
- * console.log(values)
373
- * }).then((subscriptionId) => {
374
- * console.log('balance changes subscription id: ', subscriptionId)
375
- * })
376
- * ```
377
- */
378
- subscribe(type, method, params, callback) {
379
- return this.send(method, params, false, { callback, type });
380
- }
381
- /**
382
- * @summary Allows unsubscribing to subscriptions made with [[subscribe]].
383
- */
384
- async unsubscribe(type, method, id) {
385
- const subscription = `${type}::${id}`;
386
- if (this.#subscriptions[subscription] === void 0) {
387
- return false;
388
- }
389
- delete this.#subscriptions[subscription];
390
- try {
391
- return this.isConnected && this.#websocket !== null ? this.send(method, [id]) : true;
392
- } catch {
393
- return false;
394
- }
395
- }
396
- #emit = (type, ...args) => {
397
- this.#eventemitter.emit(type, ...args);
398
- };
399
- #onSocketClose = (event) => {
400
- const error = new Error(
401
- `disconnected from ${this.endpoint}: ${event.code}:: ${event.reason || getWSErrorString(event.code)}`
402
- );
403
- if (this.#autoConnectBackoff.isActive) {
404
- if (event.code !== 1e3) log_default.error(error.message);
405
- }
406
- this.#isConnected = false;
407
- if (this.#websocket) {
408
- this.#websocket.onclose = null;
409
- this.#websocket.onerror = null;
410
- this.#websocket.onmessage = null;
411
- this.#websocket.onopen = null;
412
- this.#websocket = null;
413
- }
414
- if (this.#timeoutId) {
415
- clearInterval(this.#timeoutId);
416
- this.#timeoutId = null;
417
- }
418
- eraseRecord(this.#handlers, (h) => {
419
- try {
420
- h.callback(error, void 0);
421
- } catch (err) {
422
- log_default.error(err);
423
- }
424
- });
425
- eraseRecord(this.#waitingForId);
426
- this.#emit("disconnected");
427
- this.scheduleNextRetry();
428
- };
429
- #onSocketError = (error) => {
430
- this.#emit("error", error);
431
- };
432
- #onSocketMessage = (message) => {
433
- try {
434
- const response = JSON.parse(message.data);
435
- return response.method === void 0 ? this.#onSocketMessageResult(response) : this.#onSocketMessageSubscribe(response);
436
- } catch (e) {
437
- this.#emit("error", new Error("Invalid websocket message received", { cause: e }));
438
- }
439
- };
440
- #onSocketMessageResult = (response) => {
441
- const handler = this.#handlers[response.id];
442
- if (!handler) {
443
- return;
444
- }
445
- try {
446
- const { method, params, subscription } = handler;
447
- const result = this.#coder.decodeResponse(response);
448
- handler.callback(null, result);
449
- if (subscription) {
450
- const subId = `${subscription.type}::${result}`;
451
- this.#subscriptions[subId] = { ...subscription, method, params };
452
- if (this.#waitingForId[subId]) {
453
- this.#onSocketMessageSubscribe(this.#waitingForId[subId]);
454
- }
455
- }
456
- } catch (error) {
457
- handler.callback(error, void 0);
458
- }
459
- delete this.#handlers[response.id];
460
- };
461
- #onSocketMessageSubscribe = (response) => {
462
- const method = ALIASES[response.method] || response.method || "invalid";
463
- const subId = `${method}::${response.params.subscription}`;
464
- const handler = this.#subscriptions[subId];
465
- if (!handler) {
466
- this.#waitingForId[subId] = response;
467
- return;
468
- }
469
- delete this.#waitingForId[subId];
470
- try {
471
- const result = this.#coder.decodeResponse(response);
472
- handler.callback(null, result);
473
- } catch (error) {
474
- handler.callback(error, void 0);
475
- }
476
- };
477
- #onSocketOpen = () => {
478
- if (this.#websocket === null) {
479
- throw new Error("WebSocket cannot be null in onOpen");
480
- }
481
- this.#isConnected = true;
482
- this.#endpointsTriedSinceLastConnection = 0;
483
- this.#autoConnectBackoff.reset();
484
- this.#resubscribe();
485
- this.#emit("connected");
486
- return true;
487
- };
488
- #resubscribe = () => {
489
- const subscriptions = this.#subscriptions;
490
- this.#subscriptions = {};
491
- Promise.all(
492
- Object.keys(subscriptions).map(async (id) => {
493
- const { callback, method, params, type } = subscriptions[id];
494
- if (type.startsWith("author_")) {
495
- return;
496
- }
497
- try {
498
- await this.subscribe(type, method, params, callback);
499
- } catch (error) {
500
- log_default.error(error);
501
- }
502
- })
503
- ).catch(log_default.error);
504
- };
505
- #timeoutHandlers = () => {
506
- const now = Date.now();
507
- const ids = Object.keys(this.#handlers);
508
- for (let i = 0; i < ids.length; i++) {
509
- const handler = this.#handlers[ids[i]];
510
- if (now - handler.start > this.#timeout) {
511
- try {
512
- handler.callback(
513
- new Error(`No response received from RPC endpoint in ${this.#timeout / 1e3}s`),
514
- void 0
515
- );
516
- } catch {
517
- }
518
- delete this.#handlers[ids[i]];
519
- }
520
- }
521
- };
522
- };
523
-
524
- // src/dot/ChainConnectorDot.ts
525
- var BAD_RPC_ERRORS = {
526
- "-32097": "Rate limit exceeded",
527
- "-32098": "Capacity exceeded"
4
+ import { throwAfter } from "@talismn/util";
5
+ import { createPublicClient, createWalletClient, fallback, http } from "viem";
6
+ import { camelCase, fromPairs, toPairs } from "lodash-es";
7
+ import * as viemChains from "viem/chains";
8
+ import { createDefaultRpcTransport, createSolanaRpcFromTransport, isSolanaError } from "@solana/kit";
9
+ //#endregion
10
+ //#region src/log.ts
11
+ var log_default = anylogger("@talismn/chain-connectors");
12
+ //#endregion
13
+ //#region src/dot/ChainConnectorDot.ts
14
+ const BAD_RPC_ERRORS = {
15
+ "-32097": "Rate limit exceeded",
16
+ "-32098": "Capacity exceeded"
528
17
  };
18
+ const RESPONSE_TIMEOUT = 3e4;
19
+ const KEEP_ALIVE_INTERVAL = 2e4;
20
+ /** in-flight requests reject with DestroyedError when a connection is torn down - expected, not worth logging */
21
+ const isDestroyedError = (error) => error instanceof Error && error.name === "DestroyedError";
22
+ const STALE_NOTIFY_TIMEOUT = 3e4;
529
23
  var ChainConnectionError = class extends Error {
530
- type;
531
- chainId;
532
- constructor(chainId, options) {
533
- super(`Unable to connect to chain ${chainId}`, options);
534
- this.type = "CHAIN_CONNECTION_ERROR";
535
- this.chainId = chainId;
536
- }
24
+ type;
25
+ chainId;
26
+ constructor(chainId, options) {
27
+ super(`Unable to connect to chain ${chainId}`, options);
28
+ this.type = "CHAIN_CONNECTION_ERROR";
29
+ this.chainId = chainId;
30
+ }
537
31
  };
538
32
  var StaleRpcError = class extends Error {
539
- type;
540
- chainId;
541
- constructor(chainId, options) {
542
- super(`RPCs are stale/unavailable for chain ${chainId}`, options);
543
- this.type = "STALE_RPC_ERROR";
544
- this.chainId = chainId;
545
- }
546
- };
547
- var WebsocketAllocationExhaustedError = class extends Error {
548
- type;
549
- chainId;
550
- constructor(chainId, options) {
551
- super(
552
- `No websockets are available from the browser pool to connect to chain ${chainId}`,
553
- options
554
- );
555
- this.type = "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR";
556
- this.chainId = chainId;
557
- }
558
- };
559
- var CallerUnsubscribedError = class extends Error {
560
- type;
561
- chainId;
562
- unsubscribeMethod;
563
- constructor(chainId, unsubscribeMethod, options) {
564
- super(`Caller unsubscribed from ${chainId}`, options);
565
- this.type = "CALLER_UNSUBSCRIBED_ERROR";
566
- this.chainId = chainId;
567
- this.unsubscribeMethod = unsubscribeMethod;
568
- }
33
+ type;
34
+ chainId;
35
+ constructor(chainId, options) {
36
+ super(`RPCs are stale/unavailable for chain ${chainId}`, options);
37
+ this.type = "STALE_RPC_ERROR";
38
+ this.chainId = chainId;
39
+ }
569
40
  };
41
+ /**
42
+ * ChainConnector provides an interface similar to a websocket JSON-RPC provider, but with three points of difference:
43
+ *
44
+ * 1. ChainConnector methods all accept a `chainId` instead of an array of RPCs. RPCs are then fetched internally from chaindata.
45
+ * 2. ChainConnector creates only one socket connection per chain (via polkadot-api's ws-provider, which handles
46
+ * endpoint rotation, reconnection and stale-socket detection) and ensures that all downstream requests to a chain
47
+ * share that connection.
48
+ * 3. Subscriptions return a callable `unsubscribe` method instead of an id, and are automatically re-established
49
+ * when the provider reconnects (possibly to another endpoint).
50
+ *
51
+ * Additionally, when run on the clientside of a dapp where `window.talismanSub` is available, instead of spinning up new websocket
52
+ * connections this class will forward all requests through to the wallet backend - where another instance of this class will
53
+ * handle the websocket connections.
54
+ */
570
55
  var ChainConnectorDot = class {
571
- #chaindataChainProvider;
572
- #connectionMetaDb;
573
- #socketConnections = {};
574
- #socketKeepAliveIntervals = {};
575
- #socketUsers = {};
576
- constructor(chaindataChainProvider, connectionMetaDb) {
577
- this.#chaindataChainProvider = chaindataChainProvider;
578
- this.#connectionMetaDb = connectionMetaDb;
579
- if (this.#connectionMetaDb) {
580
- this.#chaindataChainProvider.getNetworkIds("polkadot").then((chainIds) => {
581
- this.#connectionMetaDb?.chainPriorityRpcs.where("id").noneOf(chainIds).delete();
582
- this.#connectionMetaDb?.chainBackoffInterval.where("id").noneOf(chainIds).delete();
583
- });
584
- }
585
- }
586
- /**
587
- * Creates a facade over this ChainConnector which conforms to the PJS ProviderInterface
588
- * @example // Using a chainConnector as a Provider for an ApiPromise
589
- * const provider = chainConnector.asProvider('polkadot')
590
- * const api = new ApiPromise({ provider })
591
- */
592
- asProvider(chainId) {
593
- const unsubHandler = /* @__PURE__ */ new Map();
594
- const providerFacade = {
595
- hasSubscriptions: true,
596
- isClonable: false,
597
- isConnected: true,
598
- clone: () => providerFacade,
599
- connect: () => Promise.resolve(),
600
- disconnect: () => Promise.resolve(),
601
- on: () => () => {
602
- },
603
- // biome-ignore lint/suspicious/noExplicitAny: legacy
604
- send: async (method, params, isCacheable) => await this.send(chainId, method, params, isCacheable),
605
- subscribe: async (type, method, params, cb) => {
606
- const unsubscribe = await this.subscribe(chainId, method, type, params, cb);
607
- const subscriptionId = this.getExclusiveRandomId(
608
- [...unsubHandler.keys()].map(Number)
609
- ).toString();
610
- unsubHandler.set(subscriptionId, unsubscribe);
611
- return subscriptionId;
612
- },
613
- unsubscribe: async (_type, unsubscribeMethod, subscriptionId) => {
614
- unsubHandler.get(subscriptionId)?.(unsubscribeMethod);
615
- unsubHandler.delete(subscriptionId);
616
- return true;
617
- }
618
- };
619
- return providerFacade;
620
- }
621
- // biome-ignore lint/suspicious/noExplicitAny: legacy
622
- async send(chainId, method, params, isCacheable, extraOptions) {
623
- const talismanSub = this.getTalismanSub();
624
- if (talismanSub !== void 0) {
625
- try {
626
- const chain = await this.#chaindataChainProvider.getNetworkById(chainId, "polkadot");
627
- if (!chain) throw new Error(`Chain ${chainId} not found in store`);
628
- const { genesisHash } = chain;
629
- if (typeof genesisHash !== "string")
630
- throw new Error(`Chain ${chainId} has no genesisHash in store`);
631
- return await talismanSub.send(genesisHash, method, params);
632
- } catch (error) {
633
- log_default.warn(
634
- `Failed to make wallet-proxied send request for chain ${chainId}. Falling back to plain websocket`,
635
- error
636
- );
637
- }
638
- }
639
- try {
640
- var [socketUserId, ws] = await this.connectChainSocket(chainId);
641
- } catch (error) {
642
- throw new StaleRpcError(chainId, { cause: error });
643
- }
644
- try {
645
- const timeout = 15e3;
646
- await this.waitForWs(ws, timeout);
647
- } catch (error) {
648
- await this.disconnectChainSocket(chainId, socketUserId);
649
- throw new ChainConnectionError(chainId, { cause: error });
650
- }
651
- try {
652
- const timeout = 3e4;
653
- var response = await Promise.race([
654
- ws.send(method, params, isCacheable),
655
- throwAfter(timeout, "TIMEOUT")
656
- ]);
657
- } catch (err) {
658
- const error = err;
659
- if (error?.message === "TIMEOUT") {
660
- log_default.error(`ChainConnector timeout`, { chainId, endpoint: ws.endpoint, error });
661
- await this.updateRpcPriority(chainId, ws.endpoint, "last");
662
- await this.reset(chainId);
663
- throw new Error("Timeout");
664
- }
665
- const badRpcError = BAD_RPC_ERRORS[error?.code?.toString() ?? ""];
666
- if (badRpcError) {
667
- log_default.error(`ChainConnector ${badRpcError}`, { error, chainId, endpoint: ws.endpoint });
668
- await this.updateRpcPriority(chainId, ws.endpoint, "last");
669
- await this.reset(chainId);
670
- throw new Error(badRpcError);
671
- }
672
- if (!extraOptions?.expectErrors)
673
- log_default.error(
674
- `Failed to send ${method} on chain ${chainId}
675
- params: ${JSON.stringify(params)}`,
676
- {
677
- error,
678
- endpoint: ws.endpoint
679
- }
680
- );
681
- await this.disconnectChainSocket(chainId, socketUserId);
682
- throw error;
683
- }
684
- await this.disconnectChainSocket(chainId, socketUserId);
685
- return response;
686
- }
687
- async subscribe(chainId, subscribeMethod, responseMethod, params, callback, timeout = 3e4) {
688
- const talismanSub = this.getTalismanSub();
689
- if (talismanSub !== void 0) {
690
- try {
691
- const chain = await this.#chaindataChainProvider.getNetworkById(chainId, "polkadot");
692
- if (!chain) throw new Error(`Chain ${chainId} not found in store`);
693
- const { genesisHash } = chain;
694
- if (typeof genesisHash !== "string")
695
- throw new Error(`Chain ${chainId} has no genesisHash in store`);
696
- const subscriptionId = await talismanSub.subscribe(
697
- genesisHash,
698
- subscribeMethod,
699
- responseMethod,
700
- params,
701
- callback,
702
- timeout
703
- );
704
- return (unsubscribeMethod) => talismanSub.unsubscribe(subscriptionId, unsubscribeMethod);
705
- } catch (error) {
706
- log_default.warn(
707
- `Failed to create wallet-proxied subscription for chain ${chainId}. Falling back to plain websocket`,
708
- error
709
- );
710
- }
711
- }
712
- try {
713
- var [socketUserId, ws] = await this.connectChainSocket(chainId);
714
- } catch (error) {
715
- throw new StaleRpcError(chainId, { cause: error });
716
- }
717
- const unsubDeferred = Deferred();
718
- const unsubscribe = (unsubscribeMethod) => unsubDeferred.reject(new CallerUnsubscribedError(chainId, unsubscribeMethod));
719
- const callerUnsubscribed = unsubDeferred.promise;
720
- let noMoreSocketsTimeout;
721
- (async () => {
722
- let unsubRpcStatus = null;
723
- try {
724
- const unsubStale = ws.on(
725
- "stale-rpcs",
726
- ({ nextBackoffInterval } = {}) => {
727
- callback(new StaleRpcError(chainId), null);
728
- if (this.#connectionMetaDb && nextBackoffInterval) {
729
- const id = chainId;
730
- this.#connectionMetaDb.chainBackoffInterval.put(
731
- { id, interval: nextBackoffInterval },
732
- id
733
- );
734
- }
735
- }
736
- );
737
- const unsubConnected = ws.on("connected", () => {
738
- if (this.#connectionMetaDb) this.#connectionMetaDb.chainBackoffInterval.delete(chainId);
739
- });
740
- unsubRpcStatus = () => {
741
- unsubStale();
742
- unsubConnected();
743
- };
744
- noMoreSocketsTimeout = setTimeout(
745
- () => callback(new WebsocketAllocationExhaustedError(chainId), null),
746
- 3e4
747
- // 30 seconds in ms
748
- );
749
- if (timeout) await Promise.race([this.waitForWs(ws, timeout), callerUnsubscribed]);
750
- else await Promise.race([ws.isReady, callerUnsubscribed]);
751
- clearTimeout(noMoreSocketsTimeout);
752
- } catch {
753
- clearTimeout(noMoreSocketsTimeout);
754
- unsubRpcStatus?.();
755
- await this.disconnectChainSocket(chainId, socketUserId);
756
- return;
757
- }
758
- let subscriptionId = null;
759
- let disconnected = false;
760
- let unsubscribeMethod;
761
- try {
762
- await Promise.race([
763
- ws.subscribe(responseMethod, subscribeMethod, params, callback).then((id) => {
764
- if (disconnected) {
765
- unsubscribeMethod && ws.unsubscribe(responseMethod, unsubscribeMethod, id);
766
- } else subscriptionId = id;
767
- }),
768
- callerUnsubscribed
769
- ]);
770
- } catch (error) {
771
- if (error instanceof CallerUnsubscribedError) unsubscribeMethod = error.unsubscribeMethod;
772
- unsubRpcStatus?.();
773
- disconnected = true;
774
- if (subscriptionId !== null && unsubscribeMethod)
775
- await ws.unsubscribe(responseMethod, unsubscribeMethod, subscriptionId);
776
- await this.disconnectChainSocket(chainId, socketUserId);
777
- return;
778
- }
779
- callerUnsubscribed.catch(async (error) => {
780
- let unsubscribeMethod2;
781
- if (error instanceof CallerUnsubscribedError) unsubscribeMethod2 = error.unsubscribeMethod;
782
- unsubRpcStatus?.();
783
- if (subscriptionId !== null && unsubscribeMethod2)
784
- await ws.unsubscribe(responseMethod, unsubscribeMethod2, subscriptionId);
785
- await this.disconnectChainSocket(chainId, socketUserId);
786
- }).catch((error) => log_default.warn(error));
787
- })();
788
- return unsubscribe;
789
- }
790
- /**
791
- * Kills current websocket if any
792
- * Useful after changing rpc order to make sure it's applied for futher requests
793
- */
794
- async reset(chainId) {
795
- log_default.info("ChainConnector reset", chainId);
796
- const ws = this.#socketConnections[chainId];
797
- if (!ws) return;
798
- try {
799
- clearTimeout(this.#socketKeepAliveIntervals[chainId]);
800
- delete this.#socketConnections[chainId];
801
- delete this.#socketUsers[chainId];
802
- await ws.disconnect();
803
- } catch (error) {
804
- log_default.warn(`Error occurred reseting socket ${chainId}`, error);
805
- }
806
- }
807
- /**
808
- * Wait for websocket to be ready, but don't wait forever
809
- */
810
- async waitForWs(ws, timeout = 3e4) {
811
- const timer = timeout ? sleep(timeout).then(() => {
812
- throw new Error(`RPC connect timeout reached: ${ws.endpoint}`);
813
- }) : false;
814
- await Promise.race([ws.isReady, timer].filter(isTruthy));
815
- }
816
- /**
817
- * Connect to an RPC via chainId
818
- *
819
- * The caller must call disconnectChainSocket with the returned SocketUserId once they are finished with it
820
- */
821
- async connectChainSocket(chainId) {
822
- const rpcs = await this.getEndpoints(chainId);
823
- const socketUserId = this.addSocketUser(chainId);
824
- let nextBackoffInterval;
825
- if (this.#connectionMetaDb)
826
- nextBackoffInterval = (await this.#connectionMetaDb.chainBackoffInterval.get(chainId))?.interval;
827
- if (this.#socketConnections[chainId]) return [socketUserId, this.#socketConnections[chainId]];
828
- if (rpcs.length)
829
- this.#socketConnections[chainId] = new Websocket(
830
- rpcs,
831
- void 0,
832
- void 0,
833
- nextBackoffInterval
834
- );
835
- else {
836
- throw new Error(`No healthy RPCs available for chain ${chainId}`);
837
- }
838
- if (this.#connectionMetaDb) {
839
- this.#socketConnections[chainId].on("connected", () => {
840
- if (!this.#connectionMetaDb) return;
841
- const id = chainId;
842
- const url = this.#socketConnections[chainId]?.endpoint;
843
- if (!url) return;
844
- this.updateRpcPriority(id, url, "first").catch(
845
- (err) => log_default.warn(`updateRpcPriority failed`, err)
846
- );
847
- });
848
- }
849
- ;
850
- (async () => {
851
- if (!this.#socketConnections[chainId])
852
- return log_default.warn(`ignoring ${chainId} rpc ws healthcheck initialization: ws is not defined`);
853
- await this.#socketConnections[chainId].isReady;
854
- if (this.#socketKeepAliveIntervals[chainId])
855
- clearInterval(this.#socketKeepAliveIntervals[chainId]);
856
- const intervalMs = 1e4;
857
- this.#socketKeepAliveIntervals[chainId] = setInterval(() => {
858
- if (!this.#socketConnections[chainId])
859
- return log_default.warn(`skipping ${chainId} rpc ws healthcheck: ws is not defined`);
860
- if (!this.#socketConnections[chainId].isConnected)
861
- return log_default.warn(`skipping ${chainId} rpc ws healthcheck: ws is not connected`);
862
- this.#socketConnections[chainId].send("system_health", []).catch((error) => log_default.warn(`Failed keep-alive for socket ${chainId}`, error));
863
- }, intervalMs);
864
- })();
865
- return [socketUserId, this.#socketConnections[chainId]];
866
- }
867
- async disconnectChainSocket(chainId, socketUserId) {
868
- this.removeSocketUser(chainId, socketUserId);
869
- if (this.#socketUsers[chainId].length > 0) return;
870
- if (!this.#socketConnections[chainId])
871
- return log_default.warn(`Failed to disconnect socket: socket ${chainId} not found`);
872
- try {
873
- this.#socketConnections[chainId].disconnect();
874
- } catch (error) {
875
- log_default.warn(`Error occurred disconnecting socket ${chainId}`, error);
876
- }
877
- delete this.#socketConnections[chainId];
878
- clearInterval(this.#socketKeepAliveIntervals[chainId]);
879
- delete this.#socketKeepAliveIntervals[chainId];
880
- }
881
- addSocketUser(chainId) {
882
- if (!Array.isArray(this.#socketUsers[chainId])) this.#socketUsers[chainId] = [];
883
- const socketUserId = this.getExclusiveRandomId(this.#socketUsers[chainId]);
884
- this.#socketUsers[chainId].push(socketUserId);
885
- return socketUserId;
886
- }
887
- removeSocketUser(chainId, socketUserId) {
888
- const userIndex = this.#socketUsers[chainId].indexOf(socketUserId);
889
- if (userIndex === -1)
890
- throw new Error(
891
- `Can't remove user ${socketUserId} from socket ${chainId}: user not in list ${this.#socketUsers[chainId].join(", ")}`
892
- );
893
- this.#socketUsers[chainId].splice(userIndex, 1);
894
- }
895
- /** continues to generate a random number until it finds one which is not present in the exclude list */
896
- getExclusiveRandomId(exclude = []) {
897
- let id = this.getRandomId();
898
- while (exclude.includes(id)) {
899
- id = this.getRandomId();
900
- }
901
- return id;
902
- }
903
- /** generates a random number */
904
- getRandomId() {
905
- return Math.trunc(Math.random() * 10 ** 8);
906
- }
907
- getTalismanSub() {
908
- const talismanSub = typeof window !== "undefined" && window.talismanSub;
909
- const rpcByGenesisHashSend = talismanSub?.rpcByGenesisHashSend;
910
- const rpcByGenesisHashSubscribe = talismanSub?.rpcByGenesisHashSubscribe;
911
- const rpcByGenesisHashUnsubscribe = talismanSub?.rpcByGenesisHashUnsubscribe;
912
- if (typeof rpcByGenesisHashSend !== "function") return;
913
- if (typeof rpcByGenesisHashSubscribe !== "function") return;
914
- if (typeof rpcByGenesisHashUnsubscribe !== "function") return;
915
- return {
916
- // biome-ignore lint/suspicious/noExplicitAny: legacy
917
- send: (genesisHash, method, params) => rpcByGenesisHashSend(genesisHash, method, params),
918
- subscribe: (genesisHash, subscribeMethod, responseMethod, params, callback, timeout) => rpcByGenesisHashSubscribe(
919
- genesisHash,
920
- subscribeMethod,
921
- responseMethod,
922
- params,
923
- callback,
924
- timeout
925
- ),
926
- unsubscribe: (subscriptionId, unsubscribeMethod) => rpcByGenesisHashUnsubscribe(subscriptionId, unsubscribeMethod)
927
- };
928
- }
929
- async updateRpcPriority(chainId, rpc, priority) {
930
- if (!this.#connectionMetaDb) return;
931
- const rpcs = await this.getEndpoints(chainId);
932
- if (!rpcs.includes(rpc)) throw new Error(`Unknown rpc for chain ${chainId} : ${rpc}`);
933
- const urls = rpcs.filter((r) => r !== rpc);
934
- if (priority === "first") urls.unshift(rpc);
935
- if (priority === "last") urls.push(rpc);
936
- if (!isEqual(urls, rpcs)) {
937
- await this.#connectionMetaDb.chainPriorityRpcs.put({ id: chainId, urls }, chainId);
938
- }
939
- }
940
- async getEndpoints(chainId) {
941
- const chain = await this.#chaindataChainProvider.getNetworkById(chainId, "polkadot");
942
- if (!chain) throw new Error(`Chain ${chainId} not found in store`);
943
- let rpcs = chain.rpcs.concat();
944
- const priorityRpcs = this.#connectionMetaDb ? await this.#connectionMetaDb.chainPriorityRpcs.get(chainId) : void 0;
945
- if (priorityRpcs) {
946
- rpcs = [
947
- ...priorityRpcs.urls.filter((rpc) => rpcs.includes(rpc)),
948
- ...rpcs.filter((rpc) => !priorityRpcs.urls.includes(rpc))
949
- ];
950
- }
951
- return rpcs;
952
- }
56
+ #chaindataChainProvider;
57
+ #connections = {};
58
+ #pendingConnections = {};
59
+ constructor(chaindataChainProvider) {
60
+ this.#chaindataChainProvider = chaindataChainProvider;
61
+ }
62
+ async send(chainId, method, params, _isCacheable, extraOptions) {
63
+ const talismanSub = this.getTalismanSub();
64
+ if (talismanSub !== void 0) try {
65
+ const genesisHash = await this.getGenesisHash(chainId);
66
+ return await talismanSub.send(genesisHash, method, params);
67
+ } catch (error) {
68
+ log_default.warn(`Failed to make wallet-proxied send request for chain ${chainId}. Falling back to plain websocket`, error);
69
+ }
70
+ let socketUserId;
71
+ let connection;
72
+ try {
73
+ [socketUserId, connection] = await this.acquireConnection(chainId);
74
+ } catch (error) {
75
+ throw new StaleRpcError(chainId, { cause: error });
76
+ }
77
+ try {
78
+ return await this.request(connection, method, params);
79
+ } catch (err) {
80
+ const error = err;
81
+ if (error?.message === "TIMEOUT") {
82
+ log_default.error(`ChainConnector timeout`, {
83
+ chainId,
84
+ error
85
+ });
86
+ connection.provider.switch();
87
+ throw new Error("Timeout");
88
+ }
89
+ const badRpcError = BAD_RPC_ERRORS[error?.code?.toString() ?? ""];
90
+ if (badRpcError) {
91
+ log_default.error(`ChainConnector ${badRpcError}`, {
92
+ error,
93
+ chainId
94
+ });
95
+ connection.provider.switch();
96
+ throw new Error(badRpcError);
97
+ }
98
+ if (!extraOptions?.expectErrors) log_default.error(`Failed to send ${method} on chain ${chainId}\nparams: ${JSON.stringify(params)}`, { error });
99
+ throw error;
100
+ } finally {
101
+ this.releaseConnection(chainId, socketUserId);
102
+ }
103
+ }
104
+ async subscribe(chainId, subscribeMethod, responseMethod, params, callback, timeout = 3e4) {
105
+ const talismanSub = this.getTalismanSub();
106
+ if (talismanSub !== void 0) try {
107
+ const genesisHash = await this.getGenesisHash(chainId);
108
+ const subscriptionId = await talismanSub.subscribe(genesisHash, subscribeMethod, responseMethod, params, callback, timeout);
109
+ return (unsubscribeMethod) => talismanSub.unsubscribe(subscriptionId, unsubscribeMethod);
110
+ } catch (error) {
111
+ log_default.warn(`Failed to create wallet-proxied subscription for chain ${chainId}. Falling back to plain websocket`, error);
112
+ }
113
+ let socketUserId;
114
+ let connection;
115
+ try {
116
+ [socketUserId, connection] = await this.acquireConnection(chainId);
117
+ } catch (error) {
118
+ throw new StaleRpcError(chainId, { cause: error });
119
+ }
120
+ const subscription = {
121
+ subscribeMethod,
122
+ params,
123
+ callback,
124
+ cancelRequest: null,
125
+ stopFollow: null,
126
+ serverSubId: null,
127
+ unsubscribed: false
128
+ };
129
+ connection.subscriptions.add(subscription);
130
+ if (timeout && !connection.wasConnected) {
131
+ const staleWarning = setTimeout(() => {
132
+ if (!subscription.unsubscribed && !connection.wasConnected) callback(new StaleRpcError(chainId), null);
133
+ }, timeout);
134
+ const clear = () => clearTimeout(staleWarning);
135
+ const originalCallback = subscription.callback;
136
+ subscription.callback = (error, result) => {
137
+ clear();
138
+ subscription.callback = originalCallback;
139
+ originalCallback(error, result);
140
+ };
141
+ }
142
+ this.startSubscription(connection, subscription);
143
+ return (unsubscribeMethod) => {
144
+ if (subscription.unsubscribed) return;
145
+ subscription.unsubscribed = true;
146
+ subscription.cancelRequest?.();
147
+ subscription.stopFollow?.();
148
+ const current = this.#connections[chainId];
149
+ const isLastUser = !!current && current.users.size === 1 && current.users.has(socketUserId);
150
+ if (subscription.serverSubId !== null && current && !isLastUser) try {
151
+ current.client.request(unsubscribeMethod, [subscription.serverSubId]).catch((error) => {
152
+ if (!isDestroyedError(error)) log_default.warn(`Failed to unsubscribe from ${chainId}`, error);
153
+ });
154
+ } catch (error) {
155
+ if (!isDestroyedError(error)) log_default.warn(`Failed to unsubscribe from ${chainId}`, error);
156
+ }
157
+ current?.subscriptions.delete(subscription);
158
+ this.releaseConnection(chainId, socketUserId);
159
+ };
160
+ }
161
+ /**
162
+ * Kills and recreates the connection for a chain, if any.
163
+ * Useful after changing a network's rpcs to make sure the new list is applied for further requests.
164
+ * Active subscriptions are automatically re-established on the new connection.
165
+ */
166
+ async reset(chainId) {
167
+ log_default.info("ChainConnector reset", chainId);
168
+ const connection = this.#connections[chainId];
169
+ if (!connection) return;
170
+ this.destroyConnection(connection);
171
+ delete this.#connections[chainId];
172
+ if (connection.subscriptions.size) try {
173
+ const fresh = await this.createConnection(chainId);
174
+ fresh.users = connection.users;
175
+ fresh.subscriptions = connection.subscriptions;
176
+ this.#connections[chainId] = fresh;
177
+ for (const subscription of fresh.subscriptions) this.startSubscription(fresh, subscription);
178
+ } catch (error) {
179
+ log_default.warn(`Failed to recreate connection for ${chainId} after reset`, error);
180
+ for (const subscription of connection.subscriptions) subscription.callback(new StaleRpcError(chainId, { cause: error }), null);
181
+ }
182
+ }
183
+ /** Sends a request over a connection, throwing `Error("TIMEOUT")` if no response arrives in time */
184
+ request(connection, method, params, timeoutMs = RESPONSE_TIMEOUT) {
185
+ return new Promise((resolve, reject) => {
186
+ const timer = setTimeout(() => {
187
+ cancel();
188
+ reject(/* @__PURE__ */ new Error("TIMEOUT"));
189
+ }, timeoutMs);
190
+ const cancel = connection.client._request(method, params, {
191
+ onSuccess: (result) => {
192
+ clearTimeout(timer);
193
+ resolve(result);
194
+ },
195
+ onError: (error) => {
196
+ clearTimeout(timer);
197
+ reject(error);
198
+ }
199
+ });
200
+ });
201
+ }
202
+ /** Issues the subscribe request and routes notifications to the subscription callback */
203
+ startSubscription(connection, subscription) {
204
+ subscription.serverSubId = null;
205
+ subscription.stopFollow = null;
206
+ subscription.cancelRequest = connection.client._request(subscription.subscribeMethod, subscription.params, {
207
+ onSuccess: (serverSubId, follow) => {
208
+ subscription.cancelRequest = null;
209
+ if (subscription.unsubscribed) return;
210
+ subscription.serverSubId = serverSubId;
211
+ subscription.stopFollow = follow(serverSubId, {
212
+ next: (result) => subscription.callback(null, result),
213
+ error: (error) => subscription.callback(error, null)
214
+ });
215
+ },
216
+ onError: (error) => {
217
+ subscription.cancelRequest = null;
218
+ if (!subscription.unsubscribed) subscription.callback(error, null);
219
+ }
220
+ });
221
+ }
222
+ /**
223
+ * Get (or create) the shared connection for a chain.
224
+ *
225
+ * The caller must call releaseConnection with the returned SocketUserId once they are finished with it.
226
+ */
227
+ async acquireConnection(chainId) {
228
+ let connection = this.#connections[chainId];
229
+ if (!connection) {
230
+ if (!this.#pendingConnections[chainId]) this.#pendingConnections[chainId] = this.createConnection(chainId).then((created) => {
231
+ this.#connections[chainId] = created;
232
+ return created;
233
+ }).finally(() => {
234
+ delete this.#pendingConnections[chainId];
235
+ });
236
+ connection = await this.#pendingConnections[chainId];
237
+ }
238
+ const socketUserId = this.getExclusiveRandomId([...connection.users]);
239
+ connection.users.add(socketUserId);
240
+ return [socketUserId, connection];
241
+ }
242
+ async createConnection(chainId) {
243
+ const chain = await this.#chaindataChainProvider.getNetworkById(chainId, "polkadot");
244
+ if (!chain) throw new Error(`Chain ${chainId} not found in store`);
245
+ const rpcs = chain.rpcs.concat();
246
+ if (!rpcs.length) throw new Error(`No healthy RPCs available for chain ${chainId}`);
247
+ let connection = null;
248
+ const provider = getWsProvider(rpcs, { onStatusChanged: (status) => this.handleStatusChange(connection, status) });
249
+ connection = {
250
+ chainId,
251
+ provider,
252
+ client: createClient(provider),
253
+ users: /* @__PURE__ */ new Set(),
254
+ subscriptions: /* @__PURE__ */ new Set(),
255
+ wasConnected: false,
256
+ keepAliveInterval: null,
257
+ staleTimeout: null
258
+ };
259
+ return connection;
260
+ }
261
+ handleStatusChange(connection, status) {
262
+ if (!connection) return;
263
+ if (this.#connections[connection.chainId] !== connection) return;
264
+ if (status.type === WsEvent.CONNECTED) {
265
+ if (connection.staleTimeout) {
266
+ clearTimeout(connection.staleTimeout);
267
+ connection.staleTimeout = null;
268
+ }
269
+ const isReconnect = connection.wasConnected;
270
+ connection.wasConnected = true;
271
+ if (connection.keepAliveInterval) clearInterval(connection.keepAliveInterval);
272
+ connection.keepAliveInterval = setInterval(() => {
273
+ this.request(connection, "system_health", [], KEEP_ALIVE_INTERVAL).catch((error) => {
274
+ if (!isDestroyedError(error)) log_default.warn(`Failed keep-alive for socket ${connection.chainId}`, error);
275
+ });
276
+ }, KEEP_ALIVE_INTERVAL);
277
+ if (isReconnect) for (const subscription of connection.subscriptions) {
278
+ if (subscription.unsubscribed) continue;
279
+ if (subscription.subscribeMethod.startsWith("author_")) continue;
280
+ subscription.stopFollow?.();
281
+ subscription.cancelRequest?.();
282
+ this.startSubscription(connection, subscription);
283
+ }
284
+ } else {
285
+ if (connection.keepAliveInterval) {
286
+ clearInterval(connection.keepAliveInterval);
287
+ connection.keepAliveInterval = null;
288
+ }
289
+ if (!connection.staleTimeout && connection.subscriptions.size) connection.staleTimeout = setTimeout(() => {
290
+ connection.staleTimeout = null;
291
+ for (const subscription of connection.subscriptions) if (!subscription.unsubscribed) subscription.callback(new StaleRpcError(connection.chainId), null);
292
+ }, STALE_NOTIFY_TIMEOUT);
293
+ }
294
+ }
295
+ releaseConnection(chainId, socketUserId) {
296
+ const connection = this.#connections[chainId];
297
+ if (!connection) return;
298
+ connection.users.delete(socketUserId);
299
+ if (connection.users.size > 0) return;
300
+ this.destroyConnection(connection);
301
+ delete this.#connections[chainId];
302
+ }
303
+ destroyConnection(connection) {
304
+ if (connection.keepAliveInterval) clearInterval(connection.keepAliveInterval);
305
+ if (connection.staleTimeout) clearTimeout(connection.staleTimeout);
306
+ connection.keepAliveInterval = null;
307
+ connection.staleTimeout = null;
308
+ for (const subscription of connection.subscriptions) {
309
+ subscription.stopFollow?.();
310
+ subscription.cancelRequest?.();
311
+ subscription.stopFollow = null;
312
+ subscription.cancelRequest = null;
313
+ subscription.serverSubId = null;
314
+ }
315
+ try {
316
+ connection.client.destroy();
317
+ } catch (error) {
318
+ log_default.warn(`Error occurred destroying connection ${connection.chainId}`, error);
319
+ }
320
+ }
321
+ async getGenesisHash(chainId) {
322
+ const chain = await this.#chaindataChainProvider.getNetworkById(chainId, "polkadot");
323
+ if (!chain) throw new Error(`Chain ${chainId} not found in store`);
324
+ const { genesisHash } = chain;
325
+ if (typeof genesisHash !== "string") throw new Error(`Chain ${chainId} has no genesisHash in store`);
326
+ return genesisHash;
327
+ }
328
+ /** continues to generate a random number until it finds one which is not present in the exclude list */
329
+ getExclusiveRandomId(exclude = []) {
330
+ let id = this.getRandomId();
331
+ while (exclude.includes(id)) id = this.getRandomId();
332
+ return id;
333
+ }
334
+ /** generates a random number */
335
+ getRandomId() {
336
+ return Math.trunc(Math.random() * 10 ** 8);
337
+ }
338
+ getTalismanSub() {
339
+ const talismanSub = typeof window !== "undefined" && window.talismanSub;
340
+ const rpcByGenesisHashSend = talismanSub?.rpcByGenesisHashSend;
341
+ const rpcByGenesisHashSubscribe = talismanSub?.rpcByGenesisHashSubscribe;
342
+ const rpcByGenesisHashUnsubscribe = talismanSub?.rpcByGenesisHashUnsubscribe;
343
+ if (typeof rpcByGenesisHashSend !== "function") return;
344
+ if (typeof rpcByGenesisHashSubscribe !== "function") return;
345
+ if (typeof rpcByGenesisHashUnsubscribe !== "function") return;
346
+ return {
347
+ send: (genesisHash, method, params) => rpcByGenesisHashSend(genesisHash, method, params),
348
+ subscribe: (genesisHash, subscribeMethod, responseMethod, params, callback, timeout) => rpcByGenesisHashSubscribe(genesisHash, subscribeMethod, responseMethod, params, callback, timeout),
349
+ unsubscribe: (subscriptionId, unsubscribeMethod) => rpcByGenesisHashUnsubscribe(subscriptionId, unsubscribeMethod)
350
+ };
351
+ }
953
352
  };
954
- var isEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
955
-
956
- // src/dot/ChainConnectorDotStub.ts
957
- import { WsProvider } from "@polkadot/rpc-provider";
958
- import { throwAfter as throwAfter2 } from "@talismn/util";
959
- var AUTO_CONNECT_TIMEOUT = 3e3;
960
- var TIMEOUT = 1e4;
353
+ //#endregion
354
+ //#region src/dot/ChainConnectorDotStub.ts
355
+ const TIMEOUT = 1e4;
961
356
  var ChainConnectorDotStub = class {
962
- // biome-ignore lint/correctness/noUnusedPrivateClassMembers: legacy
963
- #network;
964
- #provider;
965
- constructor(network) {
966
- this.#network = network;
967
- this.#provider = new WsProvider(network.rpcs, AUTO_CONNECT_TIMEOUT, void 0, TIMEOUT);
968
- }
969
- asProvider() {
970
- return this.#provider;
971
- }
972
- async send(_chainId, method, params, isCacheable) {
973
- await this.#provider.isReady;
974
- return this.#provider.send(method, params, isCacheable);
975
- }
976
- async subscribe(_chainId, subscribeMethod, responseMethod, params, callback, timeout) {
977
- await this.#provider.isReady;
978
- const subId = await Promise.race([
979
- throwAfter2(timeout || TIMEOUT, `Subscription timed out after ${timeout}ms`),
980
- this.#provider.subscribe(responseMethod, subscribeMethod, params, callback)
981
- ]);
982
- return (unsubscribeMethod) => {
983
- this.#provider.unsubscribe(responseMethod, unsubscribeMethod, subId);
984
- };
985
- }
986
- reset() {
987
- throw new Error("ChainConnectorDotStub does not implement reset");
988
- }
357
+ #client;
358
+ constructor(network) {
359
+ this.#client = createClient(getWsProvider(network.rpcs.concat()));
360
+ }
361
+ async send(_chainId, method, params, _isCacheable) {
362
+ return await Promise.race([this.#client.request(method, params), throwAfter(TIMEOUT, `Request ${method} timed out after ${TIMEOUT}ms`)]);
363
+ }
364
+ async subscribe(_chainId, subscribeMethod, _responseMethod, params, callback, timeout) {
365
+ let stopFollow = null;
366
+ let unsubscribed = false;
367
+ const serverSubId = await Promise.race([new Promise((resolve, reject) => {
368
+ this.#client._request(subscribeMethod, params, {
369
+ onSuccess: (subId, follow) => {
370
+ stopFollow = follow(subId, {
371
+ next: (result) => callback(null, result),
372
+ error: (error) => callback(error, null)
373
+ });
374
+ resolve(subId);
375
+ },
376
+ onError: reject
377
+ });
378
+ }), throwAfter(timeout || TIMEOUT, `Subscription timed out after ${timeout || TIMEOUT}ms`)]);
379
+ return (unsubscribeMethod) => {
380
+ if (unsubscribed) return;
381
+ unsubscribed = true;
382
+ stopFollow?.();
383
+ this.#client.request(unsubscribeMethod, [serverSubId]).catch(() => {});
384
+ };
385
+ }
386
+ reset() {
387
+ throw new Error("ChainConnectorDotStub does not implement reset");
388
+ }
389
+ destroy() {
390
+ this.#client.destroy();
391
+ }
989
392
  };
990
-
991
- // src/eth/getEvmNetworkPublicClient.ts
992
- import { createPublicClient } from "viem";
993
-
994
- // src/eth/getChainFromEvmNetwork.ts
995
- import { camelCase, fromPairs, toPairs } from "lodash-es";
996
- import * as viemChains from "viem/chains";
997
- var { zoraTestnet, ...validViemChains } = viemChains;
998
- var VIEM_CHAINS = Object.keys(validViemChains).reduce(
999
- (acc, curr) => {
1000
- const chain = validViemChains[curr];
1001
- acc[chain.id] = chain;
1002
- return acc;
1003
- },
1004
- {}
1005
- );
1006
- var chainsCache = /* @__PURE__ */ new Map();
1007
- var clearChainsCache = (networkId) => {
1008
- if (networkId) chainsCache.delete(networkId);
1009
- else chainsCache.clear();
393
+ //#endregion
394
+ //#region src/eth/getChainFromEvmNetwork.ts
395
+ const { zoraTestnet, ...validViemChains } = viemChains;
396
+ const VIEM_CHAINS = Object.keys(validViemChains).reduce((acc, curr) => {
397
+ const chain = validViemChains[curr];
398
+ acc[chain.id] = chain;
399
+ return acc;
400
+ }, {});
401
+ const chainsCache = /* @__PURE__ */ new Map();
402
+ const clearChainsCache = (networkId) => {
403
+ if (networkId) chainsCache.delete(networkId);
404
+ else chainsCache.clear();
1010
405
  };
1011
- var getChainFromEvmNetwork = (network) => {
1012
- const { symbol, decimals } = network.nativeCurrency;
1013
- if (!chainsCache.has(network.id)) {
1014
- const chainRpcs = network.rpcs ?? [];
1015
- const viemChain = VIEM_CHAINS[Number(network.id)] ?? {};
1016
- const chain = {
1017
- ...viemChain,
1018
- id: Number(network.id),
1019
- name: network.name ?? `Ethereum Chain ${network.id}`,
1020
- rpcUrls: {
1021
- public: { http: chainRpcs },
1022
- default: { http: chainRpcs }
1023
- },
1024
- nativeCurrency: {
1025
- symbol,
1026
- decimals,
1027
- name: symbol
1028
- },
1029
- contracts: {
1030
- ...viemChain.contracts,
1031
- ...network.contracts ? fromPairs(
1032
- toPairs(network.contracts).map(([name, address]) => [
1033
- camelCase(name),
1034
- { address }
1035
- ])
1036
- ) : {}
1037
- }
1038
- };
1039
- chainsCache.set(network.id, chain);
1040
- }
1041
- return chainsCache.get(network.id);
406
+ const getChainFromEvmNetwork = (network) => {
407
+ const { symbol, decimals } = network.nativeCurrency;
408
+ if (!chainsCache.has(network.id)) {
409
+ const chainRpcs = network.rpcs ?? [];
410
+ const viemChain = VIEM_CHAINS[Number(network.id)] ?? {};
411
+ const chain = {
412
+ ...viemChain,
413
+ id: Number(network.id),
414
+ name: network.name ?? `Ethereum Chain ${network.id}`,
415
+ rpcUrls: {
416
+ public: { http: chainRpcs },
417
+ default: { http: chainRpcs }
418
+ },
419
+ nativeCurrency: {
420
+ symbol,
421
+ decimals,
422
+ name: symbol
423
+ },
424
+ contracts: {
425
+ ...viemChain.contracts,
426
+ ...network.contracts ? fromPairs(toPairs(network.contracts).map(([name, address]) => [camelCase(name), { address }])) : {}
427
+ }
428
+ };
429
+ chainsCache.set(network.id, chain);
430
+ }
431
+ return chainsCache.get(network.id);
1042
432
  };
1043
-
1044
- // src/eth/getTransportForEvmNetwork.ts
1045
- import { fallback, http } from "viem";
1046
- var getTransportForEvmNetwork = (evmNetwork, options = {}) => {
1047
- if (!evmNetwork.rpcs?.length) throw new Error("No RPCs found for EVM network");
1048
- const { batch } = options;
1049
- return fallback(
1050
- evmNetwork.rpcs.map((url) => http(url, { batch, retryCount: 0 })),
1051
- { retryCount: 0 }
1052
- );
433
+ //#endregion
434
+ //#region src/eth/getTransportForEvmNetwork.ts
435
+ const getTransportForEvmNetwork = (evmNetwork, options = {}) => {
436
+ if (!evmNetwork.rpcs?.length) throw new Error("No RPCs found for EVM network");
437
+ const { batch } = options;
438
+ return fallback(evmNetwork.rpcs.map((url) => http(url, {
439
+ batch,
440
+ retryCount: 0
441
+ })), { retryCount: 0 });
1053
442
  };
1054
-
1055
- // src/eth/getEvmNetworkPublicClient.ts
1056
- var MUTLICALL_BATCH_WAIT = 25;
1057
- var MUTLICALL_BATCH_SIZE = 100;
1058
- var HTTP_BATCH_WAIT = 25;
1059
- var HTTP_BATCH_SIZE_WITH_MULTICALL = 10;
1060
- var HTTP_BATCH_SIZE_WITHOUT_MULTICALL = 30;
1061
- var publicClientCache = /* @__PURE__ */ new Map();
1062
- var clearPublicClientCache = (evmNetworkId) => {
1063
- clearChainsCache(evmNetworkId);
1064
- if (evmNetworkId) publicClientCache.delete(evmNetworkId);
1065
- else publicClientCache.clear();
443
+ //#endregion
444
+ //#region src/eth/getEvmNetworkPublicClient.ts
445
+ const MUTLICALL_BATCH_WAIT = 25;
446
+ const MUTLICALL_BATCH_SIZE = 100;
447
+ const HTTP_BATCH_WAIT = 25;
448
+ const HTTP_BATCH_SIZE_WITH_MULTICALL = 10;
449
+ const HTTP_BATCH_SIZE_WITHOUT_MULTICALL = 30;
450
+ const publicClientCache = /* @__PURE__ */ new Map();
451
+ const clearPublicClientCache = (evmNetworkId) => {
452
+ clearChainsCache(evmNetworkId);
453
+ if (evmNetworkId) publicClientCache.delete(evmNetworkId);
454
+ else publicClientCache.clear();
1066
455
  };
1067
- var getEvmNetworkPublicClient = (network) => {
1068
- const chain = getChainFromEvmNetwork(network);
1069
- if (!publicClientCache.has(network.id)) {
1070
- if (!network.rpcs.length) throw new Error("No RPCs found for Ethereum network");
1071
- const batch = chain.contracts?.multicall3 ? { multicall: { wait: MUTLICALL_BATCH_WAIT, batchSize: MUTLICALL_BATCH_SIZE } } : void 0;
1072
- const transportOptions = {
1073
- batch: {
1074
- batchSize: chain.contracts?.multicall3 ? HTTP_BATCH_SIZE_WITH_MULTICALL : HTTP_BATCH_SIZE_WITHOUT_MULTICALL,
1075
- wait: HTTP_BATCH_WAIT
1076
- }
1077
- };
1078
- const transport = getTransportForEvmNetwork(network, transportOptions);
1079
- publicClientCache.set(
1080
- network.id,
1081
- createPublicClient({
1082
- chain,
1083
- transport,
1084
- batch
1085
- })
1086
- );
1087
- }
1088
- return publicClientCache.get(network.id);
456
+ const getEvmNetworkPublicClient = (network) => {
457
+ const chain = getChainFromEvmNetwork(network);
458
+ if (!publicClientCache.has(network.id)) {
459
+ if (!network.rpcs.length) throw new Error("No RPCs found for Ethereum network");
460
+ const batch = chain.contracts?.multicall3 ? { multicall: {
461
+ wait: MUTLICALL_BATCH_WAIT,
462
+ batchSize: MUTLICALL_BATCH_SIZE
463
+ } } : void 0;
464
+ const transport = getTransportForEvmNetwork(network, { batch: {
465
+ batchSize: chain.contracts?.multicall3 ? HTTP_BATCH_SIZE_WITH_MULTICALL : HTTP_BATCH_SIZE_WITHOUT_MULTICALL,
466
+ wait: HTTP_BATCH_WAIT
467
+ } });
468
+ publicClientCache.set(network.id, createPublicClient({
469
+ chain,
470
+ transport,
471
+ batch
472
+ }));
473
+ }
474
+ return publicClientCache.get(network.id);
1089
475
  };
1090
-
1091
- // src/eth/getEvmNetworkWalletClient.ts
1092
- import { createWalletClient } from "viem";
1093
- var getEvmNetworkWalletClient = (network, options = {}) => {
1094
- const chain = getChainFromEvmNetwork(network);
1095
- const transport = getTransportForEvmNetwork(network);
1096
- return createWalletClient({ chain, transport, account: options.account });
476
+ //#endregion
477
+ //#region src/eth/getEvmNetworkWalletClient.ts
478
+ const getEvmNetworkWalletClient = (network, options = {}) => {
479
+ return createWalletClient({
480
+ chain: getChainFromEvmNetwork(network),
481
+ transport: getTransportForEvmNetwork(network),
482
+ account: options.account
483
+ });
1097
484
  };
1098
-
1099
- // src/eth/ChainConnectorEth.ts
485
+ //#endregion
486
+ //#region src/eth/ChainConnectorEth.ts
1100
487
  var ChainConnectorEth = class {
1101
- #chaindataProvider;
1102
- constructor(chaindataProvider) {
1103
- this.#chaindataProvider = chaindataProvider;
1104
- }
1105
- async getPublicClientForEvmNetwork(evmNetworkId) {
1106
- const network = await this.#chaindataProvider.getNetworkById(evmNetworkId, "ethereum");
1107
- if (!network) return null;
1108
- return getEvmNetworkPublicClient(network);
1109
- }
1110
- async getWalletClientForEvmNetwork(evmNetworkId, account) {
1111
- const network = await this.#chaindataProvider.getNetworkById(evmNetworkId, "ethereum");
1112
- if (!network) return null;
1113
- return getEvmNetworkWalletClient(network, { account });
1114
- }
1115
- clearRpcProvidersCache(evmNetworkId) {
1116
- clearPublicClientCache(evmNetworkId);
1117
- }
488
+ #chaindataProvider;
489
+ constructor(chaindataProvider) {
490
+ this.#chaindataProvider = chaindataProvider;
491
+ }
492
+ async getPublicClientForEvmNetwork(evmNetworkId) {
493
+ const network = await this.#chaindataProvider.getNetworkById(evmNetworkId, "ethereum");
494
+ if (!network) return null;
495
+ return getEvmNetworkPublicClient(network);
496
+ }
497
+ async getWalletClientForEvmNetwork(evmNetworkId, account) {
498
+ const network = await this.#chaindataProvider.getNetworkById(evmNetworkId, "ethereum");
499
+ if (!network) return null;
500
+ return getEvmNetworkWalletClient(network, { account });
501
+ }
502
+ clearRpcProvidersCache(evmNetworkId) {
503
+ clearPublicClientCache(evmNetworkId);
504
+ }
1118
505
  };
1119
-
1120
- // src/eth/ChainConnectorEthStub.ts
506
+ //#endregion
507
+ //#region src/eth/ChainConnectorEthStub.ts
1121
508
  var ChainConnectorEthStub = class {
1122
- #network;
1123
- constructor(network) {
1124
- this.#network = network;
1125
- }
1126
- async getPublicClientForEvmNetwork() {
1127
- return getEvmNetworkPublicClient(this.#network);
1128
- }
1129
- async getWalletClientForEvmNetwork(_networkId, account) {
1130
- return getEvmNetworkWalletClient(this.#network, { account });
1131
- }
1132
- clearRpcProvidersCache() {
1133
- }
509
+ #network;
510
+ constructor(network) {
511
+ this.#network = network;
512
+ }
513
+ async getPublicClientForEvmNetwork() {
514
+ return getEvmNetworkPublicClient(this.#network);
515
+ }
516
+ async getWalletClientForEvmNetwork(_networkId, account) {
517
+ return getEvmNetworkWalletClient(this.#network, { account });
518
+ }
519
+ clearRpcProvidersCache() {}
1134
520
  };
1135
-
1136
- // src/sol/getSolConnection.ts
1137
- import { Connection } from "@solana/web3.js";
1138
- var getSolConnection = (_networkId, rpcs) => {
1139
- return new Connection(rpcs[0], {
1140
- commitment: "confirmed"
1141
- });
521
+ //#endregion
522
+ //#region src/sol/getSolRpc.ts
523
+ const MAX_429_RETRIES = 5;
524
+ const BASE_BACKOFF_MS = 500;
525
+ /** Returns the delay to wait before retrying, or `null` if the error is not a retryable 429. */
526
+ const get429RetryDelay = (error, attempt) => {
527
+ if (!isSolanaError(error)) return null;
528
+ const context = error.context;
529
+ if (context.statusCode !== 429) return null;
530
+ const retryAfter = Number(context.headers?.get("retry-after"));
531
+ if (Number.isFinite(retryAfter) && retryAfter > 0) return retryAfter * 1e3;
532
+ return BASE_BACKOFF_MS * 2 ** attempt;
1142
533
  };
1143
-
1144
- // src/sol/ChainConnectorSol.ts
534
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
535
+ if (signal?.aborted) return reject(signal.reason);
536
+ const onAbort = () => {
537
+ clearTimeout(timeout);
538
+ reject(signal?.reason);
539
+ };
540
+ const timeout = setTimeout(() => {
541
+ signal?.removeEventListener("abort", onAbort);
542
+ resolve();
543
+ }, ms);
544
+ signal?.addEventListener("abort", onAbort, { once: true });
545
+ });
546
+ /**
547
+ * Wraps a transport so HTTP 429 responses are retried with backoff. Solana RPC requests are
548
+ * idempotent (`sendTransaction` is keyed by signature), so replaying a rate-limited request is safe.
549
+ */
550
+ const withRetryOn429 = (transport) => {
551
+ const wrapped = async (config) => {
552
+ for (let attempt = 0;; attempt++) try {
553
+ return await transport(config);
554
+ } catch (error) {
555
+ const delay = attempt < MAX_429_RETRIES ? get429RetryDelay(error, attempt) : null;
556
+ if (delay === null) throw error;
557
+ await sleep(delay, config.signal);
558
+ }
559
+ };
560
+ return wrapped;
561
+ };
562
+ const getSolTransport = (_networkId, rpcs) => withRetryOn429(createDefaultRpcTransport({ url: rpcs[0] }));
563
+ const getSolRpc = (networkId, rpcs) => createSolanaRpcFromTransport(getSolTransport(networkId, rpcs));
564
+ //#endregion
565
+ //#region src/sol/ChainConnectorSol.ts
1145
566
  var ChainConnectorSol = class {
1146
- #chaindataProvider;
1147
- constructor(chaindataProvider) {
1148
- this.#chaindataProvider = chaindataProvider;
1149
- }
1150
- async getConnection(networkId) {
1151
- const network = await this.#chaindataProvider.getNetworkById(networkId, "solana");
1152
- if (!network) throw new Error(`Network not found: ${networkId}`);
1153
- return getSolConnection(networkId, network.rpcs);
1154
- }
567
+ #chaindataProvider;
568
+ #transports = /* @__PURE__ */ new Map();
569
+ #rpcs = /* @__PURE__ */ new Map();
570
+ constructor(chaindataProvider) {
571
+ this.#chaindataProvider = chaindataProvider;
572
+ }
573
+ async #getNetworkRpcs(networkId) {
574
+ const network = await this.#chaindataProvider.getNetworkById(networkId, "solana");
575
+ if (!network) throw new Error(`Network not found: ${networkId}`);
576
+ return network.rpcs;
577
+ }
578
+ async getTransport(networkId) {
579
+ if (!this.#transports.has(networkId)) this.#transports.set(networkId, getSolTransport(networkId, await this.#getNetworkRpcs(networkId)));
580
+ return this.#transports.get(networkId);
581
+ }
582
+ async getRpc(networkId) {
583
+ if (!this.#rpcs.has(networkId)) this.#rpcs.set(networkId, createSolanaRpcFromTransport(await this.getTransport(networkId)));
584
+ return this.#rpcs.get(networkId);
585
+ }
586
+ /** Drops cached transports/rpcs so the next call re-reads the network's rpcs from chaindata */
587
+ clearRpcProvidersCache(networkId) {
588
+ if (networkId) {
589
+ this.#transports.delete(networkId);
590
+ this.#rpcs.delete(networkId);
591
+ } else {
592
+ this.#transports.clear();
593
+ this.#rpcs.clear();
594
+ }
595
+ }
1155
596
  };
1156
-
1157
- // src/sol/ChainConnectorSolStub.ts
597
+ //#endregion
598
+ //#region src/sol/ChainConnectorSolStub.ts
1158
599
  var ChainConnectorSolStub = class {
1159
- #connection;
1160
- constructor(networkOrConnection) {
1161
- this.#connection = "rpcs" in networkOrConnection ? getSolConnection(networkOrConnection.id, networkOrConnection.rpcs) : networkOrConnection;
1162
- }
1163
- async getConnection() {
1164
- return this.#connection;
1165
- }
1166
- };
1167
- export {
1168
- ChainConnectionError,
1169
- ChainConnectorDot,
1170
- ChainConnectorDotStub,
1171
- ChainConnectorEth,
1172
- ChainConnectorEthStub,
1173
- ChainConnectorSol,
1174
- ChainConnectorSolStub,
1175
- StaleRpcError,
1176
- WebsocketAllocationExhaustedError
600
+ #transport;
601
+ #rpc;
602
+ constructor(network) {
603
+ this.#transport = getSolTransport(network.id, network.rpcs);
604
+ this.#rpc = createSolanaRpcFromTransport(this.#transport);
605
+ }
606
+ async getRpc() {
607
+ return this.#rpc;
608
+ }
609
+ async getTransport() {
610
+ return this.#transport;
611
+ }
1177
612
  };
613
+ //#endregion
614
+ export { ChainConnectionError, ChainConnectorDot, ChainConnectorDotStub, ChainConnectorEth, ChainConnectorEthStub, ChainConnectorSol, ChainConnectorSolStub, StaleRpcError, getSolRpc, getSolTransport };
615
+
1178
616
  //# sourceMappingURL=index.mjs.map