@hippocampus-web3/blockfrost-js
Version:
A JavaScript/TypeScript SDK for interacting with the https://blockfrost.io API
1,229 lines (1,206 loc) • 166 kB
JavaScript
'use strict';
var Bottleneck = require('bottleneck');
var JSONBig = require('json-bigint');
var crypto = require('crypto');
var cardanoSerializationLibAsmjs = require('@hippocampus-web3/cardano-serialization-lib-asmjs');
var AssetFingerprint = require('@emurgo/cip14-js');
const API_URLS = {
mainnet: 'https://cardano-mainnet.blockfrost.io/api',
preview: 'https://cardano-preview.blockfrost.io/api',
preprod: 'https://cardano-preprod.blockfrost.io/api',
sanchonet: 'https://cardano-sanchonet.blockfrost.io/api',
ipfs: 'https://ipfs.blockfrost.io/api',
};
const DEFAULT_API_VERSION = 0;
const DEFAULT_BATCH_SIZE = 10;
const DEFAULT_ORDER = 'asc';
const DEFAULT_PAGINATION_PAGE_COUNT = 1;
const DEFAULT_PAGINATION_PAGE_ITEMS_COUNT = 100;
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
// default values are matching https://docs.blockfrost.io/#section/Limits
// burst 500 reqs/50s with 10req/1s cool-off
const RATE_LIMITER_DEFAULT_CONFIG = {
size: 500,
increaseInterval: 1000,
increaseAmount: 10,
};
const getLimiter = (config) => {
// see Bottleneck docs https://www.npmjs.com/package/bottleneck#constructor=
const limiter = new Bottleneck({
/*
How many jobs can be executed before the limiter stops executing jobs.
If reservoir reaches 0, no jobs will be executed until it is no longer 0.
New jobs will still be queued up.
*/
reservoir: config.size,
/*
The increment applied to reservoir when reservoirIncreaseInterval is in use.
*/
reservoirIncreaseAmount: config.increaseAmount,
/* Every reservoirRefreshInterval milliseconds, the reservoir value will be automatically updated to the value of reservoirRefreshAmount.
The reservoirRefreshInterval value should be a multiple of 250(5000 for Clustering).
*/
reservoirIncreaseInterval: config.increaseInterval,
/*
The maximum value that reservoir can reach when reservoirIncreaseInterval is in use.
*/
reservoirIncreaseMaximum: config.size,
});
limiter.on('error', function (error) {
console.error(error);
});
return limiter;
};
const isDebugEnabled = () => process.env.BLOCKFROST_DEBUG === 'true';
const validateOptions = (options) => {
var _a, _b, _c, _d;
if (!options || (!options.customBackend && !options.projectId)) {
throw Error('Missing customBackend or projectId option');
}
if (!options.projectId && !options.customBackend) {
throw Error('Missing param projectId in options');
}
if (options.version && isNaN(options.version)) {
throw Error('Param version is not a number');
}
if (options.requestTimeout && isNaN(options.requestTimeout)) {
throw Error('Param requestTimeout is not a number');
}
const debug = (_a = options.debug) !== null && _a !== void 0 ? _a : isDebugEnabled();
let rateLimiter;
if (options.rateLimiter === false) {
rateLimiter = false;
}
else if (options.rateLimiter === true ||
options.rateLimiter === undefined) {
rateLimiter = RATE_LIMITER_DEFAULT_CONFIG;
}
else if (options.rateLimiter) {
// custom config
// eslint-disable-next-line prefer-destructuring
rateLimiter = options.rateLimiter;
}
return {
customBackend: options.customBackend,
projectId: options.projectId,
network: (_b = options.network) !== null && _b !== void 0 ? _b : deriveNetworkOption(options.projectId),
rateLimiter,
version: options.version || DEFAULT_API_VERSION,
debug,
http2: (_c = options.http2) !== null && _c !== void 0 ? _c : false,
// gotOptions: options.gotOptions || undefined, // TODO: Enabley kyOptions
requestTimeout: (_d = options.requestTimeout) !== null && _d !== void 0 ? _d : 20000, // 20 seconds
// see: https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#retry
// retrySettings: options.retrySettings ?? { // TODO: Enable retry settings
// limit: 3, // retry count
// methods: ['GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'], // no retry on POST
// statusCodes: [408, 413, 429, 500, 502, 503, 504, 520, 521, 522, 524],
// errorCodes: [
// 'ETIMEDOUT',
// 'ECONNRESET',
// 'EADDRINUSE',
// 'ECONNREFUSED',
// 'EPIPE',
// 'ENOTFOUND',
// 'ENETUNREACH',
// 'EAI_AGAIN',
// 'EPROTO',
// ],
// calculateDelay: (retryObject: RetryObject) => {
// return retryObject.computedValue !== 0 ? 1000 : 0;
// },
// // maxRetryAfter: undefined,
// // backoffLimit: Number.POSITIVE_INFINITY,
// // noise: 100
// },
};
};
const deriveNetworkOption = (projectId) => {
if (!projectId)
return undefined;
if (projectId.startsWith('mainnet')) {
return 'mainnet';
}
else if (projectId.startsWith('preview')) {
return 'preview';
}
else if (projectId.startsWith('preprod')) {
return 'preprod';
}
else if (projectId.startsWith('sanchonet')) {
return 'sanchonet';
}
else if (projectId.startsWith('ipfs')) {
return 'ipfs';
}
else {
console.warn('WARNING: Old token was used without network parameter. Switching to mainnet network');
return 'mainnet';
}
};
const getAdditionalParams = (options) => {
if (!options) {
return {
from: undefined,
to: undefined,
};
}
return {
from: options.from || undefined,
to: options.to || undefined,
};
};
const getPaginationOptions = (options) => {
if (!options) {
return {
page: DEFAULT_PAGINATION_PAGE_COUNT,
count: DEFAULT_PAGINATION_PAGE_ITEMS_COUNT,
order: DEFAULT_ORDER,
};
}
return {
page: options.page || DEFAULT_PAGINATION_PAGE_COUNT,
count: options.count || DEFAULT_PAGINATION_PAGE_ITEMS_COUNT,
order: options.order || DEFAULT_ORDER,
};
};
const getAllMethodOptions = (options) => {
if (!options) {
return {
batchSize: DEFAULT_BATCH_SIZE,
order: DEFAULT_ORDER,
};
}
return {
batchSize: options.batchSize || DEFAULT_PAGINATION_PAGE_COUNT,
order: options.order || DEFAULT_ORDER,
};
};
const paginateMethod = (fn, allMethodOptions, additionalOptions) => __awaiter(void 0, void 0, void 0, function* () {
const res = [];
let page = 1;
const count = DEFAULT_PAGINATION_PAGE_ITEMS_COUNT;
const options = getAllMethodOptions(allMethodOptions);
const getSlice = () => {
const promises = [...Array(options.batchSize).keys()].map(i => fn({
page: page + i,
count,
order: options.order,
}, {
from: additionalOptions === null || additionalOptions === void 0 ? void 0 : additionalOptions.from,
to: additionalOptions === null || additionalOptions === void 0 ? void 0 : additionalOptions.to,
}));
page += options.batchSize;
return promises;
};
// eslint-disable-next-line no-constant-condition
while (true) {
const pages = yield Promise.all(getSlice());
for (const p of pages) {
res.push(...p);
if (p.length < count) {
return res; // yikes
}
}
}
});
class HTTPError extends Error {
response;
request;
options;
constructor(response, request, options) {
const code = (response.status || response.status === 0) ? response.status : '';
const title = response.statusText || '';
const status = `${code} ${title}`.trim();
const reason = status ? `status code ${status}` : 'an unknown error';
super(`Request failed with ${reason}: ${request.method} ${request.url}`);
this.name = 'HTTPError';
this.response = response;
this.request = request;
this.options = options;
}
}
class TimeoutError extends Error {
request;
constructor(request) {
super(`Request timed out: ${request.method} ${request.url}`);
this.name = 'TimeoutError';
this.request = request;
}
}
// eslint-disable-next-line @typescript-eslint/ban-types
const isObject = (value) => value !== null && typeof value === 'object';
const validateAndMerge = (...sources) => {
for (const source of sources) {
if ((!isObject(source) || Array.isArray(source)) && source !== undefined) {
throw new TypeError('The `options` argument must be an object');
}
}
return deepMerge({}, ...sources);
};
const mergeHeaders = (source1 = {}, source2 = {}) => {
const result = new globalThis.Headers(source1);
const isHeadersInstance = source2 instanceof globalThis.Headers;
const source = new globalThis.Headers(source2);
for (const [key, value] of source.entries()) {
if ((isHeadersInstance && value === 'undefined') || value === undefined) {
result.delete(key);
}
else {
result.set(key, value);
}
}
return result;
};
function newHookValue(original, incoming, property) {
return (Object.hasOwn(incoming, property) && incoming[property] === undefined)
? []
: deepMerge(original[property] ?? [], incoming[property] ?? []);
}
const mergeHooks = (original = {}, incoming = {}) => ({
beforeRequest: newHookValue(original, incoming, 'beforeRequest'),
beforeRetry: newHookValue(original, incoming, 'beforeRetry'),
afterResponse: newHookValue(original, incoming, 'afterResponse'),
beforeError: newHookValue(original, incoming, 'beforeError'),
});
// TODO: Make this strongly-typed (no `any`).
const deepMerge = (...sources) => {
let returnValue = {};
let headers = {};
let hooks = {};
for (const source of sources) {
if (Array.isArray(source)) {
if (!Array.isArray(returnValue)) {
returnValue = [];
}
returnValue = [...returnValue, ...source];
}
else if (isObject(source)) {
for (let [key, value] of Object.entries(source)) {
if (isObject(value) && key in returnValue) {
value = deepMerge(returnValue[key], value);
}
returnValue = { ...returnValue, [key]: value };
}
if (isObject(source.hooks)) {
hooks = mergeHooks(hooks, source.hooks);
returnValue.hooks = hooks;
}
if (isObject(source.headers)) {
headers = mergeHeaders(headers, source.headers);
returnValue.headers = headers;
}
}
}
return returnValue;
};
const supportsRequestStreams = (() => {
let duplexAccessed = false;
let hasContentType = false;
const supportsReadableStream = typeof globalThis.ReadableStream === 'function';
const supportsRequest = typeof globalThis.Request === 'function';
if (supportsReadableStream && supportsRequest) {
try {
hasContentType = new globalThis.Request('https://empty.invalid', {
body: new globalThis.ReadableStream(),
method: 'POST',
// @ts-expect-error - Types are outdated.
get duplex() {
duplexAccessed = true;
return 'half';
},
}).headers.has('Content-Type');
}
catch (error) {
// QQBrowser on iOS throws "unsupported BodyInit type" error (see issue #581)
if (error instanceof Error && error.message === 'unsupported BodyInit type') {
return false;
}
throw error;
}
}
return duplexAccessed && !hasContentType;
})();
const supportsAbortController = typeof globalThis.AbortController === 'function';
const supportsResponseStreams = typeof globalThis.ReadableStream === 'function';
const supportsFormData = typeof globalThis.FormData === 'function';
const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete'];
const responseTypes = {
json: 'application/json',
text: 'text/*',
formData: 'multipart/form-data',
arrayBuffer: '*/*',
blob: '*/*',
};
// The maximum value of a 32bit int (see issue #117)
const maxSafeTimeout = 2_147_483_647;
const stop = Symbol('stop');
const kyOptionKeys = {
json: true,
parseJson: true,
stringifyJson: true,
searchParams: true,
prefixUrl: true,
retry: true,
timeout: true,
hooks: true,
throwHttpErrors: true,
onDownloadProgress: true,
fetch: true,
};
const requestOptionsRegistry = {
method: true,
headers: true,
body: true,
mode: true,
credentials: true,
cache: true,
redirect: true,
referrer: true,
referrerPolicy: true,
integrity: true,
keepalive: true,
signal: true,
window: true,
dispatcher: true,
duplex: true,
priority: true,
};
const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace'];
const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
const retryAfterStatusCodes = [413, 429, 503];
const defaultRetryOptions = {
limit: 2,
methods: retryMethods,
statusCodes: retryStatusCodes,
afterStatusCodes: retryAfterStatusCodes,
maxRetryAfter: Number.POSITIVE_INFINITY,
backoffLimit: Number.POSITIVE_INFINITY,
delay: attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000,
};
const normalizeRetryOptions = (retry = {}) => {
if (typeof retry === 'number') {
return {
...defaultRetryOptions,
limit: retry,
};
}
if (retry.methods && !Array.isArray(retry.methods)) {
throw new Error('retry.methods must be an array');
}
if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
throw new Error('retry.statusCodes must be an array');
}
return {
...defaultRetryOptions,
...retry,
};
};
// `Promise.race()` workaround (#91)
async function timeout(request, init, abortController, options) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
if (abortController) {
abortController.abort();
}
reject(new TimeoutError(request));
}, options.timeout);
void options
.fetch(request, init)
.then(resolve)
.catch(reject)
.then(() => {
clearTimeout(timeoutId);
});
});
}
// https://github.com/sindresorhus/delay/tree/ab98ae8dfcb38e1593286c94d934e70d14a4e111
async function delay(ms, { signal }) {
return new Promise((resolve, reject) => {
if (signal) {
signal.throwIfAborted();
signal.addEventListener('abort', abortHandler, { once: true });
}
function abortHandler() {
clearTimeout(timeoutId);
reject(signal.reason);
}
const timeoutId = setTimeout(() => {
signal?.removeEventListener('abort', abortHandler);
resolve();
}, ms);
});
}
const findUnknownOptions = (request, options) => {
const unknownOptions = {};
for (const key in options) {
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && !(key in request)) {
unknownOptions[key] = options[key];
}
}
return unknownOptions;
};
class Ky {
static create(input, options) {
const ky = new Ky(input, options);
const function_ = async () => {
if (typeof ky._options.timeout === 'number' && ky._options.timeout > maxSafeTimeout) {
throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
}
// Delay the fetch so that body method shortcuts can set the Accept header
await Promise.resolve();
let response = await ky._fetch();
for (const hook of ky._options.hooks.afterResponse) {
// eslint-disable-next-line no-await-in-loop
const modifiedResponse = await hook(ky.request, ky._options, ky._decorateResponse(response.clone()));
if (modifiedResponse instanceof globalThis.Response) {
response = modifiedResponse;
}
}
ky._decorateResponse(response);
if (!response.ok && ky._options.throwHttpErrors) {
let error = new HTTPError(response, ky.request, ky._options);
for (const hook of ky._options.hooks.beforeError) {
// eslint-disable-next-line no-await-in-loop
error = await hook(error);
}
throw error;
}
// If `onDownloadProgress` is passed, it uses the stream API internally
/* istanbul ignore next */
if (ky._options.onDownloadProgress) {
if (typeof ky._options.onDownloadProgress !== 'function') {
throw new TypeError('The `onDownloadProgress` option must be a function');
}
if (!supportsResponseStreams) {
throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
}
return ky._stream(response.clone(), ky._options.onDownloadProgress);
}
return response;
};
const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
const result = (isRetriableMethod ? ky._retry(function_) : function_());
for (const [type, mimeType] of Object.entries(responseTypes)) {
result[type] = async () => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);
const response = await result;
if (type === 'json') {
if (response.status === 204) {
return '';
}
const arrayBuffer = await response.clone().arrayBuffer();
const responseSize = arrayBuffer.byteLength;
if (responseSize === 0) {
return '';
}
if (options.parseJson) {
return options.parseJson(await response.text());
}
}
return response[type]();
};
}
return result;
}
request;
abortController;
_retryCount = 0;
_input;
_options;
// eslint-disable-next-line complexity
constructor(input, options = {}) {
this._input = input;
this._options = {
...options,
headers: mergeHeaders(this._input.headers, options.headers),
hooks: mergeHooks({
beforeRequest: [],
beforeRetry: [],
beforeError: [],
afterResponse: [],
}, options.hooks),
method: normalizeRequestMethod(options.method ?? this._input.method ?? 'GET'),
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
prefixUrl: String(options.prefixUrl || ''),
retry: normalizeRetryOptions(options.retry),
throwHttpErrors: options.throwHttpErrors !== false,
timeout: options.timeout ?? 10_000,
fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
};
if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
throw new TypeError('`input` must be a string, URL, or Request');
}
if (this._options.prefixUrl && typeof this._input === 'string') {
if (this._input.startsWith('/')) {
throw new Error('`input` must not begin with a slash when using `prefixUrl`');
}
if (!this._options.prefixUrl.endsWith('/')) {
this._options.prefixUrl += '/';
}
this._input = this._options.prefixUrl + this._input;
}
if (supportsAbortController) {
this.abortController = new globalThis.AbortController();
const originalSignal = this._options.signal ?? this._input.signal;
if (originalSignal?.aborted) {
this.abortController.abort(originalSignal?.reason);
}
originalSignal?.addEventListener('abort', () => {
this.abortController.abort(originalSignal.reason);
});
this._options.signal = this.abortController.signal;
}
if (supportsRequestStreams) {
// @ts-expect-error - Types are outdated.
this._options.duplex = 'half';
}
if (this._options.json !== undefined) {
this._options.body = this._options.stringifyJson?.(this._options.json) ?? JSON.stringify(this._options.json);
this._options.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json');
}
this.request = new globalThis.Request(this._input, this._options);
if (this._options.searchParams) {
// eslint-disable-next-line unicorn/prevent-abbreviations
const textSearchParams = typeof this._options.searchParams === 'string'
? this._options.searchParams.replace(/^\?/, '')
: new URLSearchParams(this._options.searchParams).toString();
// eslint-disable-next-line unicorn/prevent-abbreviations
const searchParams = '?' + textSearchParams;
const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
// To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
if (((supportsFormData && this._options.body instanceof globalThis.FormData)
|| this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) {
this.request.headers.delete('content-type');
}
// The spread of `this.request` is required as otherwise it misses the `duplex` option for some reason and throws.
this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
}
}
_calculateRetryDelay(error) {
this._retryCount++;
if (this._retryCount > this._options.retry.limit || error instanceof TimeoutError) {
throw error;
}
if (error instanceof HTTPError) {
if (!this._options.retry.statusCodes.includes(error.response.status)) {
throw error;
}
const retryAfter = error.response.headers.get('Retry-After')
?? error.response.headers.get('RateLimit-Reset')
?? error.response.headers.get('X-RateLimit-Reset') // GitHub
?? error.response.headers.get('X-Rate-Limit-Reset'); // Twitter
if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
let after = Number(retryAfter) * 1000;
if (Number.isNaN(after)) {
after = Date.parse(retryAfter) - Date.now();
}
else if (after >= Date.parse('2024-01-01')) {
// A large number is treated as a timestamp (fixed threshold protects against clock skew)
after -= Date.now();
}
const max = this._options.retry.maxRetryAfter ?? after;
return after < max ? after : max;
}
if (error.response.status === 413) {
throw error;
}
}
const retryDelay = this._options.retry.delay(this._retryCount);
return Math.min(this._options.retry.backoffLimit, retryDelay);
}
_decorateResponse(response) {
if (this._options.parseJson) {
response.json = async () => this._options.parseJson(await response.text());
}
return response;
}
async _retry(function_) {
try {
return await function_();
}
catch (error) {
const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
if (this._retryCount < 1) {
throw error;
}
await delay(ms, { signal: this._options.signal });
for (const hook of this._options.hooks.beforeRetry) {
// eslint-disable-next-line no-await-in-loop
const hookResult = await hook({
request: this.request,
options: this._options,
error: error,
retryCount: this._retryCount,
});
// If `stop` is returned from the hook, the retry process is stopped
if (hookResult === stop) {
return;
}
}
return this._retry(function_);
}
}
async _fetch() {
for (const hook of this._options.hooks.beforeRequest) {
// eslint-disable-next-line no-await-in-loop
const result = await hook(this.request, this._options);
if (result instanceof Request) {
this.request = result;
break;
}
if (result instanceof Response) {
return result;
}
}
const nonRequestOptions = findUnknownOptions(this.request, this._options);
// Cloning is done here to prepare in advance for retries
const mainRequest = this.request;
this.request = mainRequest.clone();
if (this._options.timeout === false) {
return this._options.fetch(mainRequest, nonRequestOptions);
}
return timeout(mainRequest, nonRequestOptions, this.abortController, this._options);
}
/* istanbul ignore next */
_stream(response, onDownloadProgress) {
const totalBytes = Number(response.headers.get('content-length')) || 0;
let transferredBytes = 0;
if (response.status === 204) {
if (onDownloadProgress) {
onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
}
return new globalThis.Response(null, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
return new globalThis.Response(new globalThis.ReadableStream({
async start(controller) {
const reader = response.body.getReader();
if (onDownloadProgress) {
onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
}
async function read() {
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
if (onDownloadProgress) {
transferredBytes += value.byteLength;
const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
}
controller.enqueue(value);
await read();
}
await read();
},
}), {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
}
/*! MIT License © Sindre Sorhus */
const createInstance = (defaults) => {
// eslint-disable-next-line @typescript-eslint/promise-function-async
const ky = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
for (const method of requestMethods) {
// eslint-disable-next-line @typescript-eslint/promise-function-async
ky[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
}
ky.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
ky.extend = (newDefaults) => {
if (typeof newDefaults === 'function') {
newDefaults = newDefaults(defaults ?? {});
}
return createInstance(validateAndMerge(defaults, newDefaults));
};
ky.stop = stop;
return ky;
};
const ky = createInstance();
class SignatureVerificationError extends Error {
constructor(message, detail) {
super(message);
this.name = 'SignatureVerificationError';
this.message = message;
this.detail = detail;
Object.setPrototypeOf(this, SignatureVerificationError.prototype);
}
}
class BlockfrostServerError extends Error {
constructor(error) {
super(error.message);
this.name = 'BlockfrostServerError';
this.status_code = error.status_code;
this.message = error.message;
this.error = error.error;
this.url = error.url;
this.body = error.body;
Object.setPrototypeOf(this, BlockfrostServerError.prototype);
}
}
class BlockfrostClientError extends Error {
constructor(error) {
super(error.message);
this.name = 'BlockfrostClientError';
this.code = error.code;
this.message = error.message;
this.url = error.url;
Object.setPrototypeOf(this, BlockfrostClientError.prototype);
}
}
const hasProp = (
// eslint-disable-next-line @typescript-eslint/ban-types
data, prop) => {
return prop in data;
};
const isBlockfrostErrorResponse = (data) => {
// type guard for narrowing response body to an error object that should be returned by Blockfrost API
return (typeof data === 'object' &&
data !== null &&
hasProp(data, 'status_code') &&
hasProp(data, 'message') &&
hasProp(data, 'error'));
};
const handleError = (error) => {
var _a, _b;
if (error instanceof HTTPError) {
let errorInstance;
const { url } = error.request;
const responseBody = error.response.body;
if (isBlockfrostErrorResponse(responseBody)) {
errorInstance = new BlockfrostServerError(Object.assign(Object.assign({}, responseBody), { url }));
}
else {
// response.body may contain html output (eg. errors returned by nginx)
// const { statusCode } = error.response; TODO: Incomplatible KY migration
const statusText = error.message;
errorInstance = new BlockfrostServerError({
status_code: -1,
message: `${ -1}: ${statusText}`, // TODO: Incomplatible KY migration
error: statusText,
url,
// Sometimes original body can be helpful so let's forward it
// Eg. communicating directly with Cardano Submit API which returns 400 with the error from cardano-node in the body of the request)
body: error.response.body ? error.response.body : undefined,
});
}
// remove undefined body prop so it doesn't pollute string representation of the error
if (errorInstance.body === undefined) {
delete errorInstance.body;
}
return errorInstance;
}
// system errors such as -3008 ENOTFOUND and various got errors like ReadError, CacheError, MaxRedirectsError, TimeoutError,...
// https://github.com/sindresorhus/got/blob/main/documentation/8-errors.md
return new BlockfrostClientError({
code: (_a = error.name) !== null && _a !== void 0 ? _a : 'ERR_GOT_REQUEST_ERROR', // ENOTFOUND, ETIMEDOUT...
message: error.message, // getaddrinfo ENOTFOUND cardano-testnet.blockfrost.io'
url: (_b = error.request) === null || _b === void 0 ? void 0 : _b.url,
});
};
/**
* Obtains information about a specific stake account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D/get | API docs for Specific account address}
*
* @param stakeAddress - Bech32 stake address
* @returns Information about a specific stake account.
*
*/
function accounts(stakeAddress) {
return __awaiter(this, void 0, void 0, function* () {
try {
const res = yield this.instance(`accounts/${stakeAddress}`);
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about the reward history of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1rewards/get | API docs for Account reward history}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the history of a specific account.
*
*/
function accountsRewards(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/rewards`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about the whole reward history of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1rewards/get | API docs for Account reward history}
* @remarks
* Variant of `accountsRewards` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about the reward history of a specific account.
*
*/
function accountsRewardsAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsRewards(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about the history of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1history/get | API docs for Account history}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the history of a specific account.
*
*/
function accountsHistory(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/history`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about the whole history of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1history/get | API docs for Account history}
* @remarks
* Variant of `accountsHistory` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about the history of a specific account.
*
*/
function accountsHistoryAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsHistory(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about the withdrawals of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1withdrawals/get | API docs for Account withdrawal history}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the withdrawals of a specific account.
*
*/
function accountsWithdrawals(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/withdrawals`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about all withdrawals of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1withdrawals/get | API docs for Account withdrawal history}
* @remarks
* Variant of `accountsWithdrawals` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about all withdrawals of a specific account.
*
*/
function accountsWithdrawalsAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsWithdrawals(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about the MIRs of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1mirs/get | API docs for Account MIR history}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the MIRs of a specific account.
*
*/
function accountsMirs(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/mirs`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about the MIRs of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1mirs/get | API docs for Account MIR history}
* @remarks
* Variant of `accountsMirs` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about the MIRs of a specific account.
*
*/
function accountsMirsAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsMirs(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about the delegation of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1delegations/get | API docs for Account delegation history}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the delegation of a specific account.
*
*/
function accountsDelegations(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/delegations`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about all delegations of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1delegations/get | API docs for Account delegation history}
* @remarks
* Variant of `accountsDelegations` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about the delegation of a specific account.
*
*/
function accountsDelegationsAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsDelegations(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about the registrations and deregistrations of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1registrations/get | API docs for Account registration history}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the registrations and deregistrations of a specific account.
*
*/
function accountsRegistrations(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/registrations`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about the registrations and deregistrations of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1registrations/get | API docs for Account registration history}
* @remarks
* Variant of `accountsRegistrations` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about the registrations and deregistrations of a specific account.
*
*/
function accountsRegistrationsAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsRegistrations(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about the addresses of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1addresses/get | API docs for Account associated addresses}
* @remarks
* Be careful, as an account could be part of a mangled address and does not necessarily mean
* the addresses are owned by user as the account.
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Information about the addresses of a specific account.
*
*/
function accountsAddresses(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/addresses`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about all addresses of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1addresses/get | API docs for Account associated addresses}
* @remarks
* Be careful, as an account could be part of a mangled address and does not necessarily mean
* the addresses are owned by user as the account.
* @remarks
* Variant of `accountsAddresses` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Information about the addresses of a specific account.
*
*/
function accountsAddressesAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsAddresses(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains information about assets associated with addresses of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1addresses~1assets/get | API docs for Assets associated with the account addresses}
*
* @param stakeAddress - Bech32 stake address
* @param pagination - Optional, Pagination options
* @returns Assets associated with the account addresses
*
*/
function accountsAddressesAssets(stakeAddress, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const paginationOptions = getPaginationOptions(pagination);
try {
const res = yield this.instance(`accounts/${stakeAddress}/addresses/assets`, {
searchParams: {
page: paginationOptions.page,
count: paginationOptions.count,
order: paginationOptions.order,
},
});
return yield res.json();
}
catch (error) {
throw handleError(error);
}
});
}
/**
* Obtains information about all assets associated with addresses of a specific account.
* @see {@link https://docs.blockfrost.io/#tag/Cardano-Accounts/paths/~1accounts~1%7Bstake_address%7D~1addresses~1assets/get | API docs for Assets associated with the account addresses}
* @remarks
* Variant of `accountsAddressesAssets` method for fetching all pages with built-in requests batching
*
* @param stakeAddress - Bech32 stake address
* @param allMethodOptions - Optional, Options for request batching
* @returns Assets associated with the account addresses
*
*/
function accountsAddressesAssetsAll(stakeAddress, allMethodOptions) {
return __awaiter(this, void 0, void 0, function* () {
return paginateMethod(pagination => this.accountsAddressesAssets(stakeAddress, pagination), allMethodOptions);
});
}
/**
* Obtains summed details about all addresses associated with a given account.
* @see {@lin