@talismn/chain-connectors 0.1.0 → 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/LICENSE +253 -674
- package/dist/index.d.mts +117 -106
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +117 -106
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +617 -1190
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +590 -1152
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -17
package/dist/index.js
CHANGED
|
@@ -1,1223 +1,650 @@
|
|
|
1
|
-
"
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
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.0",
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
-
|
|
510
|
+
//#endregion
|
|
511
|
+
//#region src/eth/ChainConnectorEth.ts
|
|
1144
512
|
var ChainConnectorEth = class {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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
|
-
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/eth/ChainConnectorEthStub.ts
|
|
1165
533
|
var ChainConnectorEthStub = class {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
-
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/sol/ChainConnectorSolStub.ts
|
|
1202
624
|
var ChainConnectorSolStub = class {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|