@imikailoby/sats
Version:
Tiny non-custodial Bitcoin SDK (TS) — keys, addresses, PSBT, provider chain
55 lines (54 loc) • 2.05 kB
JavaScript
import { withTimeout } from "../utils/http";
import { ProviderError, TimeoutError } from "../core/errors";
/**
* Create a provider that tries the given providers in order with simple backoff and timeout.
* Backoff schedule: 0ms, 250ms, 500ms.
*/
export function createProviderChain(...providersOrOptions) {
// Options can be passed as the last argument
let timeoutMs = 4000;
let backoffMs = [0, 250, 500];
const providers = [];
providersOrOptions.forEach((item, idx) => {
if (typeof item.getAddressUtxos === "function") {
providers.push(item);
}
else if (idx === providersOrOptions.length - 1) {
const opts = item;
if (typeof opts.timeoutMs === "number")
timeoutMs = opts.timeoutMs;
if (Array.isArray(opts.backoffMs) && opts.backoffMs.length > 0)
backoffMs = opts.backoffMs;
}
});
async function tryAll(fn) {
let lastErr;
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
const delay = backoffMs[Math.min(i, backoffMs.length - 1)] ?? 0;
if (delay > 0)
await new Promise((r) => {
const t = setTimeout(r, delay);
t?.unref?.();
});
try {
return await withTimeout(fn(provider), timeoutMs);
}
catch (e) {
lastErr = e instanceof TimeoutError ? e : new ProviderError(`provider[${i}] failed`, e);
}
}
throw lastErr ?? new ProviderError("no providers available");
}
return {
getAddressUtxos(addr) {
return tryAll((p) => p.getAddressUtxos(addr));
},
getAddressBalance(addr) {
return tryAll((p) => p.getAddressBalance(addr));
},
broadcast(rawHex) {
return tryAll((p) => (p.broadcast ? p.broadcast(rawHex) : Promise.reject(new ProviderError("broadcast not supported"))));
},
};
}