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