@unruggable/gateways
Version:
Trustless Ethereum Multichain CCIP-Read Gateway
98 lines (97 loc) • 3.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GatewayProvider = void 0;
const utils_js_1 = require("./utils.cjs");
const providers_1 = require("ethers/providers");
const utils_1 = require("ethers/utils");
function shouldNeverBatch(payload) {
// see: LineaProver.ts:fetchProofs()
return payload.method === 'linea_getProof';
}
class GatewayProvider extends providers_1.JsonRpcProvider {
chain;
// convenience
static async http(url) {
const res = await fetch(url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
method: 'eth_chainId',
params: [],
id: 1,
jsonrpc: '2.0',
}),
});
const { result, error } = await res.json();
if (!res.ok || error)
throw new Error(error?.message ?? 'expected rpc');
return new this(new utils_1.FetchRequest(url), BigInt(result));
}
constructor(fr, chain,
// 20250107: infura/alchemy lowered limit to 10
// TODO: make this a provider-specific setting?
batchMaxCount = 10) {
super(fr, chain, { staticNetwork: true, batchMaxCount });
this.chain = chain;
}
async _send(payload) {
if (Array.isArray(payload) && payload.length > 1) {
// determine if any of the payloads cant be batched
const ps = [];
const batch = [];
const { promise, resolve, reject } = (0, utils_js_1.withResolvers)();
const ret = payload.map((x) => {
if (shouldNeverBatch(x)) {
const p = this._sendWithRetry(x).then((x) => x[0]);
ps.push(p);
return p;
}
else {
const i = batch.length;
batch.push(x);
return promise.then((v) => v[i]);
}
});
if (batch.length) {
ps.push(this._sendWithRetry(batch).then(resolve, reject));
await Promise.all(ps);
}
return Promise.all(ret);
}
return this._sendWithRetry(payload);
}
async _sendWithRetry(payload) {
let backoff = 250;
for (let attempt = 0;; attempt++) {
try {
// ethers bug: return type is wrong
// expected: (JsonRpcResult | JsonRpcError)[]
const results = await super._send(payload);
if (!results.some((x) => x.error?.code === 429)) {
// handle alchemy weirdness
// note: this is only supposed to happen over WebSocket
// note: there is no header "retry-after"
// https://docs.alchemy.com/reference/throughput
return results;
}
}
catch (err) {
if (err instanceof Error &&
!Array.isArray(payload) &&
payload.method == 'eth_getProof' &&
'code' in err &&
(err.code === 'UNKNOWN_ERROR' || err.code === 'TIMEOUT')) {
// eth_getProof failed
// this is to deal with erigon polygon rpcs
}
else {
throw err;
}
}
this.emit('debug', { action: 'retry', attempt });
await new Promise((ful) => setTimeout(ful, backoff));
backoff *= 2;
}
}
}
exports.GatewayProvider = GatewayProvider;