@talismn/api
Version:
A lib for spinning up multiple polkadot.js or lightclient instances and being able to perform aggregate queries
296 lines (295 loc) • 11.1 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __export = (target, all) => {
__markAsModule(target);
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
__export(exports, {
default: () => TalismanConnect
});
var import_types = __toModule(require("@polkadot/types"));
var import_util_crypto = __toModule(require("@polkadot/util-crypto"));
var import_chaindata_js = __toModule(require("@talismn/chaindata-js"));
var import_util = __toModule(require("@talismn/util"));
var import_lodash = __toModule(require("lodash"));
const systemHash = "26aa394eea5630e07c48ae0c9558cef7";
const accountHash = "b99d880ec681799c0cf30e8886371da9";
const AccountInfo = JSON.stringify({
nonce: "u32",
consumer: "u32",
providers: "u32",
sufficients: "u32",
data: {
free: "u128",
reserved: "u128",
miscFrozen: "u128",
feeFrozen: "u128"
}
});
const registry = new import_types.TypeRegistry();
registry.register({ AccountInfo });
const pathsToEndpoints = {
balance: {
endpoint: `0x${systemHash}${accountHash}%s`,
params: [String]
}
};
class TalismanConnect {
constructor(chainId, rpcs) {
this.nativeToken = null;
this.wsCreated = false;
this.wsHandlers = {};
this.wsNextHandlerId = 1;
this.wsSubscriptions = {};
this.wsLatestUnhandledSubscriptionData = {};
this.chainId = chainId;
this.rpcs = rpcs;
return this;
}
getChainData() {
return __async(this, null, function* () {
var _a;
if (!((_a = this.rpcs) == null ? void 0 : _a.length)) {
const chain = yield import_chaindata_js.default.chain(this.chainId);
this.rpcs = chain.rpcs;
}
if (!this.nativeToken) {
const chain = yield import_chaindata_js.default.chain(this.chainId);
this.nativeToken = chain.nativeToken;
}
return {
rpcs: this.rpcs,
nativeToken: this.nativeToken
};
});
}
connect() {
return __async(this, null, function* () {
if (!this.chainId)
return;
yield this.getChainData();
return;
});
}
subscribe(path, args, callback) {
return __async(this, null, function* () {
if (this.chainId === null) {
console.warn("ignoring subscription request: chainId not set");
return null;
}
for (const rpc of this.rpcs) {
try {
if (!rpc)
throw new Error("failed to create subscription: rpc required");
if (!rpc.startsWith("wss://") && !rpc.startsWith("ws://")) {
throw new Error("failed to create subscription: ws or wss rpc protocol required");
}
const endpoint = (0, import_lodash.get)(pathsToEndpoints, path).endpoint;
if (!endpoint)
return null;
const method = "state_subscribeStorage";
const params = [
args.map((args2) => (0, import_util.decodeAnyAddress)(args2[0])).map((addressBytes) => blake2Concat(addressBytes).replace("0x", "")).map((addressHash) => endpoint.replace("%s", `${addressHash}`))
];
const response = yield this._wsRpcFetch(rpc, method, params);
const result = JSON.parse(response).result;
const subscriptionId = result;
this.wsSubscriptions[subscriptionId] = callback;
if (this.wsLatestUnhandledSubscriptionData[subscriptionId]) {
this.wsLatestUnhandledSubscriptionData[subscriptionId].forEach(callback);
delete this.wsLatestUnhandledSubscriptionData[subscriptionId];
}
const unsubscribe = () => __async(this, null, function* () {
const method2 = "state_unsubscribeStorage";
const params2 = [subscriptionId];
const response2 = yield this._wsRpcFetch(rpc, method2, params2);
const result2 = JSON.parse(response2).result;
return result2;
});
return unsubscribe;
} catch (error) {
console.error(`Failed rpc subscription via ${rpc}`, error);
continue;
}
}
throw new Error(`Failed rpc subscription via all rpcs for chain ${this.chainId} callpath ${path}`);
});
}
call(path, params, format) {
return __async(this, null, function* () {
if (!this.chainId)
return null;
for (const rpc of this.rpcs) {
try {
if (!rpc)
throw new Error("rpc required");
if (rpc.startsWith("wss://") || rpc.startsWith("ws://")) {
return this._callWs(rpc, path, params, format);
}
if (rpc.startsWith("https://") || rpc.startsWith("http://")) {
return this._callHttp(rpc, path, params, format);
}
console.warn("TalismanConnect.call not implemented. Try TalismanConnect.subscribe instead!");
return null;
} catch (error) {
console.error(`Failed rpc call via ${rpc}`, error);
continue;
}
}
throw new Error(`Failed rpc call via all rpcs for chain ${this.chainId} callpath ${path}`);
});
}
_callWs(rpc, path, args, format) {
return __async(this, null, function* () {
const endpoint = (0, import_lodash.get)(pathsToEndpoints, path).endpoint;
if (!endpoint)
return null;
const address = args[0];
const addressBytes = (0, import_util.decodeAnyAddress)(address);
const addressHash = blake2Concat(addressBytes).replace("0x", "");
const method = "state_getStorage";
const params = [endpoint.replace("%s", `${addressHash}`)];
const response = yield this._wsRpcFetch(rpc, method, params);
const result = JSON.parse(response).result;
const output = (0, import_types.createType)(registry, AccountInfo, result);
return format({ chainId: this.chainId, nativeToken: this.nativeToken, output });
});
}
_callHttp(rpc, _path, _args, _format) {
return __async(this, null, function* () {
throw new Error(`rpc via http not yet implemented: ${rpc}`);
});
}
_wsRpcFetch(url, method, params) {
return new Promise((resolve, reject) => __async(this, null, function* () {
if (!this.wsCreated)
this.wsCreated = this._createSocket(url);
try {
yield this.wsCreated;
} catch (error) {
this.wsCreated = false;
reject(error);
}
if (this.ws === void 0)
return reject("failed to create websocket connection");
const id = this._nextWsHandlerId();
this.wsHandlers[id] = (data) => {
if (data === null)
return reject();
resolve(data);
};
const payload = JSON.stringify({ id, jsonrpc: "2.0", method, params });
this.ws.send(payload);
}));
}
_nextWsHandlerId() {
const id = this.wsNextHandlerId;
this.wsNextHandlerId = (this.wsNextHandlerId + 1) % 999999;
return id;
}
_createSocket(url) {
return new Promise((resolve, reject) => {
const socket = new WebSocket(url);
let skipHealthCheck = true;
const keepaliveInterval = 1e4;
const healthcheck = setInterval(() => {
!skipHealthCheck && this._wsRpcFetch(url, "system_health", []);
}, keepaliveInterval);
socket.onopen = () => {
this.ws = socket;
skipHealthCheck = false;
resolve();
};
socket.onmessage = (message) => {
var _a;
const data = JSON.parse(message.data);
const isSubscriptionUpdate = data.method !== void 0 && typeof data.params.subscription === "string";
if (isSubscriptionUpdate) {
const subscriptionId = data.params.subscription;
const formatChange = ([reference, change]) => ({
chainId: this.chainId,
nativeToken: this.nativeToken,
reference,
output: (0, import_types.createType)(registry, AccountInfo, change)
});
const handler2 = this.wsSubscriptions[subscriptionId];
if (!handler2) {
console.warn(`caching result for subscription ${subscriptionId}: no handler registered for this subscription id`);
this.wsLatestUnhandledSubscriptionData[subscriptionId] = data.params.result.changes.map(formatChange);
return;
}
data.params.result.changes.map(formatChange).forEach(handler2);
return;
}
const id = (_a = JSON.parse(message.data)) == null ? void 0 : _a.id;
if (!id) {
console.warn("ignoring ws message with no id", data);
return;
}
if (!this.wsHandlers[id]) {
console.warn("ignoring ws message with unknown id", data);
return;
}
const handler = this.wsHandlers[id];
delete this.wsHandlers[id];
handler(message.data);
};
socket.onerror = reject;
socket.onclose = () => {
clearInterval(healthcheck);
this.ws = void 0;
this.wsCreated = false;
const handlers = Object.values(this.wsHandlers);
this.wsHandlers = {};
handlers.forEach((handler) => handler(null));
this.wsSubscriptions = {};
reject();
};
});
}
}
TalismanConnect.type = "TALISMANCONNECT";
function blake2Concat(input) {
const inputHash = (0, import_util_crypto.blake2AsHex)(input, 128);
const inputHex = [...input].map((x) => x.toString(16).padStart(2, "0")).join("");
return `${inputHash}${inputHex}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {});