@ensnode/ensnode-sdk
Version:
A utility library for interacting with ENSNode and ENS data
1,424 lines (1,371 loc) • 149 kB
JavaScript
// src/ensapi/config/deserialize.ts
import { prettifyError as prettifyError2, ZodError } from "zod/v4";
// src/ensapi/config/zod-schemas.ts
import { z as z3 } from "zod/v4";
// src/ensindexer/config/zod-schemas.ts
import z2 from "zod/v4";
// src/shared/account-id.ts
import { isAddressEqual } from "viem";
var accountIdEqual = (a, b) => {
return a.chainId === b.chainId && isAddressEqual(a.address, b.address);
};
// src/shared/address.ts
function asLowerCaseAddress(address) {
return address.toLowerCase();
}
// src/shared/cache/lru-cache.ts
var LruCache = class {
_cache = /* @__PURE__ */ new Map();
_capacity;
/**
* Create a new LRU cache with the given capacity.
*
* @param capacity The maximum number of items in the cache. If set to 0, the cache is effectively disabled.
* @throws Error if capacity is not a non-negative integer.
*/
constructor(capacity) {
if (!Number.isInteger(capacity)) {
throw new Error(
`LruCache requires capacity to be an integer but a capacity of ${capacity} was requested.`
);
}
if (capacity < 0) {
throw new Error(
`LruCache requires a non-negative capacity but a capacity of ${capacity} was requested.`
);
}
this._capacity = capacity;
}
set(key, value) {
this._cache.set(key, value);
if (this._cache.size > this._capacity) {
const oldestKey = this._cache.keys().next().value;
this._cache.delete(oldestKey);
}
}
get(key) {
const value = this._cache.get(key);
if (value) {
this._cache.delete(key);
this._cache.set(key, value);
}
return value;
}
clear() {
this._cache.clear();
}
get size() {
return this._cache.size;
}
get capacity() {
return this._capacity;
}
};
// src/shared/cache/swr-cache.ts
import { secondsToMilliseconds } from "date-fns";
import { getUnixTime } from "date-fns/getUnixTime";
// src/shared/deserialize.ts
import { prettifyError } from "zod/v4";
// src/shared/zod-schemas.ts
import { AccountId as CaipAccountId } from "caip";
import { isAddress as isAddress2, isHex as isHex2, size } from "viem";
import z from "zod/v4";
// src/ens/index.ts
import { getENSRootChainId } from "@ensnode/datasources";
// src/ens/coin-type.ts
import {
coinTypeToEvmChainId as _coinTypeToEvmChainId,
evmChainIdToCoinType as _evmChainIdToCoinType
} from "@ensdomains/address-encoder/utils";
var ETH_COIN_TYPE = 60;
var DEFAULT_EVM_CHAIN_ID = 0;
var DEFAULT_EVM_COIN_TYPE = 2147483648;
var coinTypeToEvmChainId = (coinType) => {
if (coinType === ETH_COIN_TYPE) return 1;
return _coinTypeToEvmChainId(coinType);
};
var evmChainIdToCoinType = (chainId) => {
if (chainId === 1) return ETH_COIN_TYPE;
return _evmChainIdToCoinType(chainId);
};
var bigintToCoinType = (value) => {
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
throw new Error(`'${value}' cannot represent as CoinType, it is too large.`);
}
return Number(value);
};
// src/ens/constants.ts
import { namehash } from "viem";
var ROOT_NODE = namehash("");
var ETH_NODE = namehash("eth");
var BASENAMES_NODE = namehash("base.eth");
var LINEANAMES_NODE = namehash("linea.eth");
var ADDR_REVERSE_NODE = namehash("addr.reverse");
// src/ens/dns-encoded-name.ts
import { bytesToString, hexToBytes } from "viem";
function decodeDNSEncodedLiteralName(packet) {
return decodeDNSEncodedName(packet);
}
function decodeDNSEncodedName(packet) {
const segments = [];
const bytes = hexToBytes(packet);
if (bytes.length === 0) throw new Error(`Packet is empty.`);
let offset = 0;
while (offset < bytes.length) {
const len = bytes[offset];
if (len === void 0) {
throw new Error(`Invariant: bytes[offset] is undefined after offset < bytes.length check.`);
}
if (len < 0 || len > 255) {
throw new Error(
`Invariant: this should be literally impossible, but an unsigned byte was less than zero or greater than 255. The value in question is ${len}`
);
}
if (len === 0) break;
const segment = bytesToString(bytes.subarray(offset + 1, offset + len + 1));
segments.push(segment);
offset += len + 1;
}
if (offset >= bytes.length) throw new Error(`Overflow, offset >= bytes.length`);
if (offset !== bytes.length - 1) throw new Error(`Junk at end of name`);
return segments;
}
// src/ens/labelhash.ts
import { isHex } from "viem";
function isLabelHash(maybeLabelHash) {
const expectedLength = maybeLabelHash.length === 66;
const expectedEncoding = isHex(maybeLabelHash);
const expectedCasing = maybeLabelHash === maybeLabelHash.toLowerCase();
return expectedLength && expectedEncoding && expectedCasing;
}
// src/ens/encode-labelhash.ts
var encodeLabelHash = (labelHash) => `[${labelHash.slice(2)}]`;
function isEncodedLabelHash(maybeEncodedLabelHash) {
const expectedFormatting = maybeEncodedLabelHash.startsWith("[") && maybeEncodedLabelHash.endsWith("]");
const includesLabelHash = isLabelHash(`0x${maybeEncodedLabelHash.slice(1, -1)}`);
return expectedFormatting && includesLabelHash;
}
// src/ens/is-normalized.ts
import { normalize } from "viem/ens";
function isNormalizedName(name) {
try {
return name === normalize(name);
} catch {
return false;
}
}
function isNormalizedLabel(label) {
if (label === "") return false;
if (label.includes(".")) return false;
try {
return label === normalize(label);
} catch {
return false;
}
}
// src/ens/names.ts
import { ens_beautify } from "@adraffy/ens-normalize";
var ENS_ROOT = "";
var getNameHierarchy = (name) => name.split(".").map((_, i, labels) => labels.slice(i).join("."));
var getParentNameFQDN = (name) => {
if (name === ENS_ROOT) {
throw new Error("There is no parent name for ENS Root.");
}
const labels = name.split(".");
if (labels.length === 1) {
return ENS_ROOT;
}
return labels.slice(1).join(".");
};
var beautifyName = (name) => {
const beautifiedLabels = name.split(".").map((label) => {
if (isNormalizedLabel(label)) {
return ens_beautify(label);
} else {
return label;
}
});
return beautifiedLabels.join(".");
};
// src/ens/parse-reverse-name.ts
import { hexToBigInt, isAddress } from "viem";
var REVERSE_NAME_REGEX = /^([0-9a-fA-F]+)\.([0-9a-f]{1,64}|addr|default)\.reverse$/;
var parseAddressLabel = (addressLabel) => {
const maybeAddress = `0x${addressLabel}`;
if (!isAddress(maybeAddress)) {
throw new Error(`Invalid EVM address "${maybeAddress}"`);
}
return asLowerCaseAddress(maybeAddress);
};
var parseCoinTypeLabel = (coinTypeLabel) => {
if (coinTypeLabel === "default") return DEFAULT_EVM_COIN_TYPE;
if (coinTypeLabel === "addr") return ETH_COIN_TYPE;
return bigintToCoinType(hexToBigInt(`0x${coinTypeLabel}`));
};
function parseReverseName(name) {
const match = name.match(REVERSE_NAME_REGEX);
if (!match) return null;
try {
const [, addressLabel, coinTypeLabel] = match;
if (!addressLabel) return null;
if (!coinTypeLabel) return null;
return {
address: parseAddressLabel(addressLabel),
coinType: parseCoinTypeLabel(coinTypeLabel)
};
} catch {
return null;
}
}
// src/ens/reverse-name.ts
var addrReverseLabel = (address) => address.slice(2);
var coinTypeReverseLabel = (coinType) => coinType.toString(16);
function reverseName(address, coinType) {
const label = addrReverseLabel(address);
const middle = (() => {
switch (coinType) {
case ETH_COIN_TYPE:
return "addr";
case DEFAULT_EVM_COIN_TYPE:
return "default";
default:
return coinTypeReverseLabel(coinType);
}
})();
return `${label}.${middle}.reverse`;
}
// src/ens/subname-helpers.ts
import { concat, keccak256, toHex } from "viem";
var makeSubdomainNode = (labelHash, node) => keccak256(concat([node, labelHash]));
var uint256ToHex32 = (num) => toHex(num, { size: 32 });
// src/ens/types.ts
import { ENSNamespaceIds } from "@ensnode/datasources";
// src/shared/currencies.ts
var CurrencyIds = {
ETH: "ETH",
USDC: "USDC",
DAI: "DAI"
};
var currencyInfo = {
[CurrencyIds.ETH]: {
id: CurrencyIds.ETH,
name: "ETH",
decimals: 18
},
[CurrencyIds.USDC]: {
id: CurrencyIds.USDC,
name: "USDC",
decimals: 6
},
[CurrencyIds.DAI]: {
id: CurrencyIds.DAI,
name: "Dai Stablecoin",
decimals: 18
}
};
function getCurrencyInfo(currencyId) {
return currencyInfo[currencyId];
}
function priceEth(amount) {
return {
amount,
currency: CurrencyIds.ETH
};
}
function priceUsdc(amount) {
return {
amount,
currency: CurrencyIds.USDC
};
}
function priceDai(amount) {
return {
amount,
currency: CurrencyIds.DAI
};
}
function isPriceCurrencyEqual(priceA, priceB) {
return priceA.currency === priceB.currency;
}
function isPriceEqual(priceA, priceB) {
return isPriceCurrencyEqual(priceA, priceB) && priceA.amount === priceB.amount;
}
function addPrices(...prices) {
const firstPrice = prices[0];
const allPricesInSameCurrency = prices.every((price) => isPriceCurrencyEqual(firstPrice, price));
if (allPricesInSameCurrency === false) {
throw new Error("All prices must have the same currency to be added together.");
}
const { currency } = firstPrice;
return prices.reduce(
(acc, price) => ({
amount: acc.amount + price.amount,
currency
}),
{
amount: 0n,
currency: firstPrice.currency
}
);
}
// src/shared/reinterpretation.ts
import { labelhash as labelToLabelHash } from "viem";
function reinterpretLabel(label) {
if (label === "") {
throw new Error(
`Cannot reinterpret an empty label that violates the invariants of an InterpretedLabel.`
);
}
if (isEncodedLabelHash(label)) return label;
if (isNormalizedLabel(label)) return label;
return encodeLabelHash(labelToLabelHash(label));
}
function reinterpretName(name) {
if (name === "") return name;
const interpretedLabels = name.split(".");
const reinterpretedLabels = interpretedLabels.map(reinterpretLabel);
const reinterpretedName = reinterpretedLabels.join(".");
return reinterpretedName;
}
// src/shared/zod-schemas.ts
var makeFiniteNonNegativeNumberSchema = (valueLabel = "Value") => z.number({
// NOTE: Zod's implementation of `number` automatically rejects NaN and Infinity values.
// and therefore the finite check is implicit.
error: `${valueLabel} must be a finite number.`
}).nonnegative({
error: `${valueLabel} must be a non-negative number (>=0).`
});
var makeIntegerSchema = (valueLabel = "Value") => z.int({
error: `${valueLabel} must be an integer.`
});
var makePositiveIntegerSchema = (valueLabel = "Value") => makeIntegerSchema(valueLabel).positive({
error: `${valueLabel} must be a positive integer (>0).`
});
var makeNonNegativeIntegerSchema = (valueLabel = "Value") => makeIntegerSchema(valueLabel).nonnegative({
error: `${valueLabel} must be a non-negative integer (>=0).`
});
var makeDurationSchema = (valueLabel = "Value") => z.coerce.number({
error: `${valueLabel} must be a number.`
}).pipe(makeNonNegativeIntegerSchema(valueLabel));
var makeChainIdSchema = (valueLabel = "Chain ID") => makePositiveIntegerSchema(valueLabel).transform((val) => val);
var makeChainIdStringSchema = (valueLabel = "Chain ID String") => z.string({ error: `${valueLabel} must be a string representing a chain ID.` }).pipe(z.coerce.number({ error: `${valueLabel} must represent a positive integer (>0).` })).pipe(makeChainIdSchema(`The numeric value represented by ${valueLabel}`));
var makeLowercaseAddressSchema = (valueLabel = "EVM address") => z.string().check((ctx) => {
if (!isAddress2(ctx.value)) {
ctx.issues.push({
code: "custom",
message: `${valueLabel} must be a valid EVM address`,
input: ctx.value
});
}
}).transform((val) => asLowerCaseAddress(val));
var makeDatetimeSchema = (valueLabel = "Datetime string") => z.iso.datetime({ error: `${valueLabel} must be a string in ISO 8601 format.` }).transform((v) => new Date(v));
var makeUnixTimestampSchema = (valueLabel = "Timestamp") => makeIntegerSchema(valueLabel);
var makeUrlSchema = (valueLabel = "Value") => z.url({
error: `${valueLabel} must be a valid URL string (e.g., http://localhost:8080 or https://example.com).`,
abort: true
}).transform((v) => new URL(v));
var makeBlockNumberSchema = (valueLabel = "Block number") => makeNonNegativeIntegerSchema(valueLabel);
var makeBlockrangeSchema = (valueLabel = "Value") => z.strictObject(
{
startBlock: makeBlockNumberSchema(`${valueLabel}.startBlock`).optional(),
endBlock: makeBlockNumberSchema(`${valueLabel}.endBlock`).optional()
},
{
error: `${valueLabel} must be a valid Blockrange object.`
}
).refine(
(v) => {
if (v.startBlock && v.endBlock) {
return v.startBlock <= v.endBlock;
}
return true;
},
{ error: `${valueLabel}: startBlock must be before or equal to endBlock` }
);
var makeBlockRefSchema = (valueLabel = "Value") => z.strictObject(
{
timestamp: makeUnixTimestampSchema(`${valueLabel}.timestamp`),
number: makeBlockNumberSchema(`${valueLabel}.number`)
},
{
error: `${valueLabel} must be a valid BlockRef object.`
}
);
var makeENSNamespaceIdSchema = (valueLabel = "ENSNamespaceId") => z.enum(ENSNamespaceIds, {
error() {
return `Invalid ${valueLabel}. Supported ENS namespace IDs are: ${Object.keys(ENSNamespaceIds).join(", ")}`;
}
});
var makePriceAmountSchema = (valueLabel = "Amount") => z.coerce.bigint({
error: `${valueLabel} must represent a bigint.`
}).nonnegative({
error: `${valueLabel} must not be negative.`
});
var makePriceCurrencySchema = (currency, valueLabel = "Price Currency") => z.strictObject({
amount: makePriceAmountSchema(`${valueLabel} amount`),
currency: z.literal(currency, {
error: `${valueLabel} currency must be set to '${currency}'.`
})
});
var makePriceEthSchema = (valueLabel = "Price ETH") => makePriceCurrencySchema(CurrencyIds.ETH, valueLabel).transform((v) => v);
var makeAccountIdSchema = (valueLabel = "AccountId") => z.strictObject({
chainId: makeChainIdSchema(`${valueLabel} chain ID`),
address: makeLowercaseAddressSchema(`${valueLabel} address`)
});
var makeAccountIdStringSchema = (valueLabel = "Account ID String") => z.coerce.string().transform((v) => {
const result = new CaipAccountId(v);
return {
chainId: Number(result.chainId.reference),
address: result.address
};
}).pipe(makeAccountIdSchema(valueLabel));
var makeHexStringSchema = (options, valueLabel = "String representation of bytes array") => z.string().check(function invariant_isHexEncoded(ctx) {
if (!isHex2(ctx.value)) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: `${valueLabel} must be a hexadecimal value which starts with '0x'.`
});
}
}).transform((v) => v).check(function invariant_encodesRequiredBytesCount(ctx) {
const expectedBytesCount = options.bytesCount;
const actualBytesCount = size(ctx.value);
if (actualBytesCount !== expectedBytesCount) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: `${valueLabel} must represent exactly ${expectedBytesCount} bytes. Currently represented bytes count: ${actualBytesCount}.`
});
}
});
var makeNodeSchema = (valueLabel = "Node") => makeHexStringSchema({ bytesCount: 32 }, valueLabel);
var makeTransactionHashSchema = (valueLabel = "Transaction hash") => makeHexStringSchema({ bytesCount: 32 }, valueLabel);
var makeReinterpretedNameSchema = (valueLabel = "Reinterpreted Name") => z.string().transform((v) => v).check((ctx) => {
try {
reinterpretName(ctx.value);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
ctx.issues.push({
code: "custom",
input: ctx.value,
message: `${valueLabel} cannot be reinterpreted: ${errorMessage}`
});
}
}).transform(reinterpretName);
// src/shared/deserialize.ts
function deserializeChainId(maybeChainId, valueLabel) {
const schema = makeChainIdStringSchema(valueLabel);
const parsed = schema.safeParse(maybeChainId);
if (parsed.error) {
throw new Error(`Cannot deserialize ChainId:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeDatetime(maybeDatetime, valueLabel) {
const schema = makeDatetimeSchema(valueLabel);
const parsed = schema.safeParse(maybeDatetime);
if (parsed.error) {
throw new Error(`Cannot deserialize Datetime:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeUnixTimestamp(maybeTimestamp, valueLabel) {
const schema = makeUnixTimestampSchema(valueLabel);
const parsed = schema.safeParse(maybeTimestamp);
if (parsed.error) {
throw new Error(`Cannot deserialize Unix Timestamp:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeUrl(maybeUrl, valueLabel) {
const schema = makeUrlSchema(valueLabel);
const parsed = schema.safeParse(maybeUrl);
if (parsed.error) {
throw new Error(`Cannot deserialize URL:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeBlockNumber(maybeBlockNumber, valueLabel) {
const schema = makeBlockNumberSchema(valueLabel);
const parsed = schema.safeParse(maybeBlockNumber);
if (parsed.error) {
throw new Error(`Cannot deserialize BlockNumber:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeBlockrange(maybeBlockrange, valueLabel) {
const schema = makeBlockrangeSchema(valueLabel);
const parsed = schema.safeParse(maybeBlockrange);
if (parsed.error) {
throw new Error(`Cannot deserialize Blockrange:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeBlockRef(maybeBlockRef, valueLabel) {
const schema = makeBlockRefSchema(valueLabel);
const parsed = schema.safeParse(maybeBlockRef);
if (parsed.error) {
throw new Error(`Cannot deserialize BlockRef:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function deserializeDuration(maybeDuration, valueLabel) {
const schema = makeDurationSchema(valueLabel);
const parsed = schema.safeParse(maybeDuration);
if (parsed.error) {
throw new RangeError(`Cannot deserialize Duration:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
function parseAccountId(maybeAccountId, valueLabel) {
const schema = makeAccountIdStringSchema(valueLabel);
const parsed = schema.safeParse(maybeAccountId);
if (parsed.error) {
throw new RangeError(`Cannot deserialize AccountId:
${prettifyError(parsed.error)}
`);
}
return parsed.data;
}
// src/shared/datetime.ts
function durationBetween(start, end) {
return deserializeDuration(end - start, "Duration");
}
function addDuration(timestamp, duration) {
return deserializeUnixTimestamp(timestamp + duration, "UnixTimestamp");
}
// src/shared/cache/swr-cache.ts
var SWRCache = class {
constructor(options) {
this.options = options;
if (options.proactiveRevalidationInterval) {
this.backgroundInterval = setInterval(
() => this.revalidate(),
secondsToMilliseconds(options.proactiveRevalidationInterval)
);
}
if (options.proactivelyInitialize) this.revalidate();
}
cache = null;
inProgressRevalidate = null;
backgroundInterval = null;
async revalidate() {
if (!this.inProgressRevalidate) {
this.inProgressRevalidate = this.options.fn().then((result) => {
this.cache = {
result,
updatedAt: getUnixTime(/* @__PURE__ */ new Date())
};
}).catch((error) => {
if (!this.cache) {
this.cache = {
// ensure thrown value is always an Error instance
result: error instanceof Error ? error : new Error(String(error)),
updatedAt: getUnixTime(/* @__PURE__ */ new Date())
};
}
}).finally(() => {
this.inProgressRevalidate = null;
});
}
return this.inProgressRevalidate;
}
/**
* Read the most recently cached result from the `SWRCache`.
*
* @returns a `ValueType` that was most recently successfully returned by `fn` or `Error` if `fn`
* has never successfully returned.
*/
async read() {
if (!this.cache) await this.revalidate();
if (!this.cache) throw new Error("never");
if (durationBetween(this.cache.updatedAt, getUnixTime(/* @__PURE__ */ new Date())) > this.options.ttl) {
this.revalidate();
}
return this.cache.result;
}
/**
* Destroys the background revalidation interval, if exists.
*/
destroy() {
if (this.backgroundInterval) {
clearInterval(this.backgroundInterval);
this.backgroundInterval = null;
}
}
};
// src/shared/cache/ttl-cache.ts
import { getUnixTime as getUnixTime2 } from "date-fns/getUnixTime";
var TtlCache = class {
_cache = /* @__PURE__ */ new Map();
_ttl;
/**
* Create a new TTL cache with the given TTL.
*
* @param ttl Time-to-live duration in seconds. Items expire after this duration.
*/
constructor(ttl) {
this._ttl = ttl;
}
_cleanup() {
const now = getUnixTime2(/* @__PURE__ */ new Date());
for (const [key, entry] of this._cache.entries()) {
if (entry.expiresAt <= now) {
this._cache.delete(key);
}
}
}
set(key, value) {
this._cleanup();
const expiresAt = addDuration(getUnixTime2(/* @__PURE__ */ new Date()), this._ttl);
this._cache.set(key, { value, expiresAt });
}
get(key) {
this._cleanup();
const entry = this._cache.get(key);
if (!entry) {
return void 0;
}
if (entry.expiresAt <= getUnixTime2(/* @__PURE__ */ new Date())) {
this._cache.delete(key);
return void 0;
}
return entry.value;
}
clear() {
this._cache.clear();
}
get size() {
this._cleanup();
return this._cache.size;
}
get capacity() {
return Number.MAX_SAFE_INTEGER;
}
has(key) {
this._cleanup();
const entry = this._cache.get(key);
if (!entry) {
return false;
}
if (entry.expiresAt <= getUnixTime2(/* @__PURE__ */ new Date())) {
this._cache.delete(key);
return false;
}
return true;
}
delete(key) {
return this._cache.delete(key);
}
};
// src/shared/collections.ts
var uniq = (arr) => [...new Set(arr)];
// src/shared/datasource-contract.ts
import { maybeGetDatasource } from "@ensnode/datasources";
var maybeGetDatasourceContract = (namespaceId, datasourceName, contractName) => {
const datasource = maybeGetDatasource(namespaceId, datasourceName);
if (!datasource) return void 0;
const address = datasource.contracts[contractName]?.address;
if (address === void 0 || Array.isArray(address)) return void 0;
return {
chainId: datasource.chain.id,
address
};
};
var getDatasourceContract = (namespaceId, datasourceName, contractName) => {
const contract = maybeGetDatasourceContract(namespaceId, datasourceName, contractName);
if (!contract) {
throw new Error(
`Expected contract not found for ${namespaceId} ${datasourceName} ${contractName}`
);
}
return contract;
};
// src/shared/labelhash.ts
import { keccak256 as keccak2562, stringToBytes } from "viem";
var labelhashLiteralLabel = (label) => keccak2562(stringToBytes(label));
// src/shared/interpretation.ts
function literalLabelToInterpretedLabel(label) {
if (isNormalizedLabel(label)) return label;
return encodeLabelHash(labelhashLiteralLabel(label));
}
function literalLabelsToInterpretedName(labels) {
return labels.map(literalLabelToInterpretedLabel).join(".");
}
function interpretedLabelsToInterpretedName(labels) {
return labels.join(".");
}
function literalLabelsToLiteralName(labels) {
return labels.join(".");
}
// src/shared/null-bytes.ts
var hasNullByte = (value) => value.indexOf("\0") !== -1;
var stripNullBytes = (value) => value.replaceAll("\0", "");
// src/shared/numbers.ts
function bigIntToNumber(n) {
if (n < Number.MIN_SAFE_INTEGER) {
throw new Error(
`The bigint '${n.toString()}' value is too low to be to converted into a number.'`
);
}
if (n > Number.MAX_SAFE_INTEGER) {
throw new Error(
`The bigint '${n.toString()}' value is too high to be to converted into a number.'`
);
}
return Number(n);
}
// src/shared/serialize.ts
import { AccountId as CaipAccountId2 } from "caip";
function serializeChainId(chainId) {
return chainId.toString();
}
function serializeDatetime(datetime) {
return datetime.toISOString();
}
function serializeUrl(url) {
return url.toString();
}
function serializePrice(price) {
return {
currency: price.currency,
amount: price.amount.toString()
};
}
function serializePriceEth(price) {
return serializePrice(price);
}
function formatAccountId(accountId) {
return CaipAccountId2.format({
chainId: { namespace: "eip155", reference: accountId.chainId.toString() },
address: accountId.address
}).toLowerCase();
}
// src/shared/url.ts
function isHttpProtocol(url) {
return ["http:", "https:"].includes(url.protocol);
}
function isWebSocketProtocol(url) {
return ["ws:", "wss:"].includes(url.protocol);
}
// src/ensindexer/config/is-subgraph-compatible.ts
import { ENSNamespaceIds as ENSNamespaceIds2 } from "@ensnode/datasources";
// src/ensindexer/config/types.ts
var PluginName = /* @__PURE__ */ ((PluginName2) => {
PluginName2["Subgraph"] = "subgraph";
PluginName2["Basenames"] = "basenames";
PluginName2["Lineanames"] = "lineanames";
PluginName2["ThreeDNS"] = "threedns";
PluginName2["ProtocolAcceleration"] = "protocol-acceleration";
PluginName2["Registrars"] = "registrars";
PluginName2["TokenScope"] = "tokenscope";
return PluginName2;
})(PluginName || {});
// src/ensindexer/config/is-subgraph-compatible.ts
function isSubgraphCompatible(config) {
const onlySubgraphPluginActivated = config.plugins.length === 1 && config.plugins[0] === "subgraph" /* Subgraph */;
const isSubgraphLabelSet = config.labelSet.labelSetId === "subgraph" && config.labelSet.labelSetVersion === 0;
const isEnsTestEnvLabelSet = config.labelSet.labelSetId === "ens-test-env" && config.labelSet.labelSetVersion === 0;
const labelSetIsSubgraphCompatible = isSubgraphLabelSet || config.namespace === ENSNamespaceIds2.EnsTestEnv && isEnsTestEnvLabelSet;
return onlySubgraphPluginActivated && labelSetIsSubgraphCompatible;
}
// src/ensindexer/config/validations.ts
function invariant_ensDbVersionIsSameAsEnsIndexerVersion(ctx) {
const versionInfo = ctx.value;
if (versionInfo.ensDb !== versionInfo.ensIndexer) {
ctx.issues.push({
code: "custom",
input: versionInfo,
message: "`ensDb` version must be same as `ensIndexer` version"
});
}
}
// src/ensindexer/config/zod-schemas.ts
var makeIndexedChainIdsSchema = (valueLabel = "Indexed Chain IDs") => z2.array(makeChainIdSchema(valueLabel), {
error: `${valueLabel} must be an array.`
}).min(1, { error: `${valueLabel} list must include at least one element.` }).transform((v) => new Set(v));
var makePluginsListSchema = (valueLabel = "Plugins") => z2.array(z2.string(), {
error: `${valueLabel} must be a list of strings.`
}).min(1, {
error: `${valueLabel} must be a list of strings with at least one string value`
}).refine((arr) => arr.length === uniq(arr).length, {
error: `${valueLabel} cannot contain duplicate values.`
});
var makeDatabaseSchemaNameSchema = (valueLabel = "Database schema name") => z2.string({ error: `${valueLabel} must be a string` }).trim().nonempty({
error: `${valueLabel} is required and must be a non-empty string.`
});
var makeLabelSetIdSchema = (valueLabel) => {
return z2.string({ error: `${valueLabel} must be a string` }).min(1, { error: `${valueLabel} must be 1-50 characters long` }).max(50, { error: `${valueLabel} must be 1-50 characters long` }).regex(/^[a-z-]+$/, {
error: `${valueLabel} can only contain lowercase letters (a-z) and hyphens (-)`
});
};
var makeLabelSetVersionSchema = (valueLabel) => {
return z2.coerce.number({ error: `${valueLabel} must be an integer.` }).pipe(makeNonNegativeIntegerSchema(valueLabel));
};
var makeFullyPinnedLabelSetSchema = (valueLabel = "Label set") => {
let valueLabelLabelSetId = valueLabel;
let valueLabelLabelSetVersion = valueLabel;
if (valueLabel === "LABEL_SET") {
valueLabelLabelSetId = "LABEL_SET_ID";
valueLabelLabelSetVersion = "LABEL_SET_VERSION";
} else {
valueLabelLabelSetId = `${valueLabel}.labelSetId`;
valueLabelLabelSetVersion = `${valueLabel}.labelSetVersion`;
}
return z2.object({
labelSetId: makeLabelSetIdSchema(valueLabelLabelSetId),
labelSetVersion: makeLabelSetVersionSchema(valueLabelLabelSetVersion)
});
};
var makeNonEmptyStringSchema = (valueLabel = "Value") => z2.string().nonempty({ error: `${valueLabel} must be a non-empty string.` });
var makeENSIndexerVersionInfoSchema = (valueLabel = "Value") => z2.strictObject(
{
nodejs: makeNonEmptyStringSchema(),
ponder: makeNonEmptyStringSchema(),
ensDb: makeNonEmptyStringSchema(),
ensIndexer: makeNonEmptyStringSchema(),
ensNormalize: makeNonEmptyStringSchema(),
ensRainbow: makeNonEmptyStringSchema(),
ensRainbowSchema: makePositiveIntegerSchema()
},
{
error: `${valueLabel} must be a valid ENSIndexerVersionInfo object.`
}
).check(invariant_ensDbVersionIsSameAsEnsIndexerVersion);
function invariant_isSubgraphCompatibleRequirements(ctx) {
const { value: config } = ctx;
if (config.isSubgraphCompatible && !isSubgraphCompatible(config)) {
ctx.issues.push({
code: "custom",
input: config,
message: `'isSubgraphCompatible' requires only the '${"subgraph" /* Subgraph */}' plugin to be active and labelSet must be {labelSetId: "subgraph", labelSetVersion: 0}`
});
}
}
var makeENSIndexerPublicConfigSchema = (valueLabel = "ENSIndexerPublicConfig") => z2.object({
labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`),
indexedChainIds: makeIndexedChainIdsSchema(`${valueLabel}.indexedChainIds`),
isSubgraphCompatible: z2.boolean({ error: `${valueLabel}.isSubgraphCompatible` }),
namespace: makeENSNamespaceIdSchema(`${valueLabel}.namespace`),
plugins: makePluginsListSchema(`${valueLabel}.plugins`),
databaseSchemaName: makeDatabaseSchemaNameSchema(`${valueLabel}.databaseSchemaName`),
versionInfo: makeENSIndexerVersionInfoSchema(`${valueLabel}.versionInfo`)
}).check(invariant_isSubgraphCompatibleRequirements);
// src/ensapi/config/zod-schemas.ts
var TheGraphCannotFallbackReasonSchema = z3.enum({
NotSubgraphCompatible: "not-subgraph-compatible",
NoApiKey: "no-api-key",
NoSubgraphUrl: "no-subgraph-url"
});
var TheGraphFallbackSchema = z3.strictObject({
canFallback: z3.boolean(),
reason: TheGraphCannotFallbackReasonSchema.nullable()
});
function makeENSApiPublicConfigSchema(valueLabel) {
const label = valueLabel ?? "ENSApiPublicConfig";
return z3.strictObject({
version: z3.string().min(1, `${label}.version must be a non-empty string`),
theGraphFallback: TheGraphFallbackSchema,
ensIndexerPublicConfig: makeENSIndexerPublicConfigSchema(`${label}.ensIndexerPublicConfig`)
});
}
// src/ensapi/config/deserialize.ts
function deserializeENSApiPublicConfig(maybeConfig, valueLabel) {
const schema = makeENSApiPublicConfigSchema(valueLabel);
try {
return schema.parse(maybeConfig);
} catch (error) {
if (error instanceof ZodError) {
throw new Error(`Cannot deserialize ENSApiPublicConfig:
${prettifyError2(error)}
`);
}
throw error;
}
}
// src/ensindexer/config/deserialize.ts
import { prettifyError as prettifyError3 } from "zod/v4";
function deserializeENSIndexerPublicConfig(maybeConfig, valueLabel) {
const schema = makeENSIndexerPublicConfigSchema(valueLabel);
const parsed = schema.safeParse(maybeConfig);
if (parsed.error) {
throw new Error(`Cannot deserialize ENSIndexerPublicConfig:
${prettifyError3(parsed.error)}
`);
}
return parsed.data;
}
// src/ensindexer/config/label-utils.ts
import { hexToBytes as hexToBytes2 } from "viem";
function labelHashToBytes(labelHash) {
try {
if (labelHash.length !== 66) {
throw new Error(`Invalid labelHash length ${labelHash.length} characters (expected 66)`);
}
if (labelHash !== labelHash.toLowerCase()) {
throw new Error("Labelhash must be in lowercase");
}
if (!labelHash.startsWith("0x")) {
throw new Error("Labelhash must be 0x-prefixed");
}
const bytes = hexToBytes2(labelHash);
if (bytes.length !== 32) {
throw new Error(`Invalid labelHash length ${bytes.length} bytes (expected 32)`);
}
return bytes;
} catch (e) {
if (e instanceof Error) {
throw e;
}
throw new Error("Invalid hex format");
}
}
// src/ensindexer/config/labelset-utils.ts
function buildLabelSetId(maybeLabelSetId) {
return makeLabelSetIdSchema("LabelSetId").parse(maybeLabelSetId);
}
function buildLabelSetVersion(maybeLabelSetVersion) {
return makeLabelSetVersionSchema("LabelSetVersion").parse(maybeLabelSetVersion);
}
function buildEnsRainbowClientLabelSet(labelSetId, labelSetVersion) {
if (labelSetVersion !== void 0 && labelSetId === void 0) {
throw new Error("When a labelSetVersion is defined, labelSetId must also be defined.");
}
return { labelSetId, labelSetVersion };
}
function validateSupportedLabelSetAndVersion(serverSet, clientSet) {
if (clientSet.labelSetId === void 0) {
return;
}
if (serverSet.labelSetId !== clientSet.labelSetId) {
throw new Error(
`Server label set ID "${serverSet.labelSetId}" does not match client's requested label set ID "${clientSet.labelSetId}".`
);
}
if (clientSet.labelSetVersion !== void 0 && serverSet.highestLabelSetVersion < clientSet.labelSetVersion) {
throw new Error(
`Server highest label set version ${serverSet.highestLabelSetVersion} is less than client's requested version ${clientSet.labelSetVersion} for label set ID "${clientSet.labelSetId}".`
);
}
}
// src/ensindexer/config/parsing.ts
function parseNonNegativeInteger(maybeNumber) {
const trimmed = maybeNumber.trim();
if (!trimmed) {
throw new Error("Input cannot be empty");
}
if (trimmed === "-0") {
throw new Error("Negative zero is not a valid non-negative integer");
}
const num = Number(maybeNumber);
if (Number.isNaN(num)) {
throw new Error(`"${maybeNumber}" is not a valid number`);
}
if (!Number.isFinite(num)) {
throw new Error(`"${maybeNumber}" is not a finite number`);
}
if (!Number.isInteger(num)) {
throw new Error(`"${maybeNumber}" is not an integer`);
}
if (num < 0) {
throw new Error(`"${maybeNumber}" is not a non-negative integer`);
}
return num;
}
// src/ensindexer/config/serialize.ts
function serializeIndexedChainIds(indexedChainIds) {
return Array.from(indexedChainIds);
}
function serializeENSIndexerPublicConfig(config) {
const {
labelSet,
indexedChainIds,
databaseSchemaName,
isSubgraphCompatible: isSubgraphCompatible2,
namespace,
plugins,
versionInfo
} = config;
return {
labelSet,
indexedChainIds: serializeIndexedChainIds(indexedChainIds),
databaseSchemaName,
isSubgraphCompatible: isSubgraphCompatible2,
namespace,
plugins,
versionInfo
};
}
// src/ensindexer/indexing-status/deserialize.ts
import { prettifyError as prettifyError4 } from "zod/v4";
// src/ensindexer/indexing-status/zod-schemas.ts
import z4 from "zod/v4";
// src/ensindexer/indexing-status/types.ts
var ChainIndexingConfigTypeIds = {
/**
* Represents that indexing of the chain should be performed for an indefinite range.
*/
Indefinite: "indefinite",
/**
* Represents that indexing of the chain should be performed for a definite range.
*/
Definite: "definite"
};
var ChainIndexingStatusIds = {
/**
* Represents that indexing of the chain is not ready to begin yet because:
* - ENSIndexer is in its initialization phase and the data to build a
* "true" {@link ChainIndexingSnapshot} for the chain is still being loaded; or
* - ENSIndexer is using an omnichain indexing strategy and the
* `omnichainIndexingCursor` is <= `config.startBlock.timestamp` for the chain's
* {@link ChainIndexingSnapshot}.
*/
Queued: "chain-queued",
/**
* Represents that indexing of the chain is in progress and under a special
* "backfill" phase that optimizes for accelerated indexing until reaching the
* "fixed target" `backfillEndBlock`.
*/
Backfill: "chain-backfill",
/**
* Represents that the "backfill" phase of indexing the chain is completed
* and that the chain is configured to be indexed for an indefinite range.
* Therefore, indexing of the chain remains indefinitely in progress where
* ENSIndexer will continuously work to discover and index new blocks as they
* are added to the chain across time.
*/
Following: "chain-following",
/**
* Represents that indexing of the chain is completed as the chain is configured
* to be indexed for a definite range and the indexing of all blocks through
* that definite range is completed.
*/
Completed: "chain-completed"
};
var OmnichainIndexingStatusIds = {
/**
* Represents that omnichain indexing is not ready to begin yet because
* ENSIndexer is in its initialization phase and the data to build a "true"
* {@link OmnichainIndexingStatusSnapshot} is still being loaded.
*/
Unstarted: "omnichain-unstarted",
/**
* Represents that omnichain indexing is in an overall "backfill" status because
* - At least one indexed chain has a `chainStatus` of
* {@link ChainIndexingStatusIds.Backfill}; and
* - No indexed chain has a `chainStatus` of {@link ChainIndexingStatusIds.Following}.
*/
Backfill: "omnichain-backfill",
/**
* Represents that omnichain indexing is in an overall "following" status because
* at least one indexed chain has a `chainStatus` of
* {@link ChainIndexingStatusIds.Following}.
*/
Following: "omnichain-following",
/**
* Represents that omnichain indexing has completed because all indexed chains have
* a `chainStatus` of {@link ChainIndexingStatusIds.Completed}.
*/
Completed: "omnichain-completed"
};
var CrossChainIndexingStrategyIds = {
/**
* Represents that the indexing of events across all indexed chains will
* proceed in a deterministic "omnichain" ordering by block timestamp, chain ID,
* and block number.
*
* This strategy is "deterministic" in that the order of processing cross-chain indexed
* events and each resulting indexed data state transition recorded in ENSDb is always
* the same for each ENSIndexer instance operating with an equivalent
* `ENSIndexerConfig` and ENSIndexer version. However it also has the drawbacks of:
* - increased indexing latency that must wait for the slowest indexed chain to
* add new blocks or to discover new blocks through the configured RPCs.
* - if any indexed chain gets "stuck" due to chain or RPC failures, all indexed chains
* will be affected.
*/
Omnichain: "omnichain"
};
// src/shared/block-ref.ts
function isBefore(blockA, blockB) {
return blockA.number < blockB.number && blockA.timestamp < blockB.timestamp;
}
function isEqualTo(blockA, blockB) {
return blockA.number === blockB.number && blockA.timestamp === blockB.timestamp;
}
function isBeforeOrEqualTo(blockA, blockB) {
return isBefore(blockA, blockB) || isEqualTo(blockA, blockB);
}
// src/ensindexer/indexing-status/helpers.ts
function getOmnichainIndexingStatus(chains) {
if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing(chains)) {
return OmnichainIndexingStatusIds.Following;
}
if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(chains)) {
return OmnichainIndexingStatusIds.Backfill;
}
if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted(chains)) {
return OmnichainIndexingStatusIds.Unstarted;
}
if (checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted(chains)) {
return OmnichainIndexingStatusIds.Completed;
}
throw new Error(`Unable to determine omnichain indexing status for provided chains.`);
}
function getTimestampForLowestOmnichainStartBlock(chains) {
const earliestKnownBlockTimestamps = chains.map(
(chain) => chain.config.startBlock.timestamp
);
return Math.min(...earliestKnownBlockTimestamps);
}
function getTimestampForHighestOmnichainKnownBlock(chains) {
const latestKnownBlockTimestamps = [];
for (const chain of chains) {
switch (chain.chainStatus) {
case ChainIndexingStatusIds.Queued:
if (chain.config.configType === ChainIndexingConfigTypeIds.Definite && chain.config.endBlock) {
latestKnownBlockTimestamps.push(chain.config.endBlock.timestamp);
}
break;
case ChainIndexingStatusIds.Backfill:
latestKnownBlockTimestamps.push(chain.backfillEndBlock.timestamp);
break;
case ChainIndexingStatusIds.Completed:
latestKnownBlockTimestamps.push(chain.latestIndexedBlock.timestamp);
break;
case ChainIndexingStatusIds.Following:
latestKnownBlockTimestamps.push(chain.latestKnownBlock.timestamp);
break;
}
}
return Math.max(...latestKnownBlockTimestamps);
}
function getOmnichainIndexingCursor(chains) {
if (chains.length === 0) {
throw new Error(`Unable to determine omnichain indexing cursor when no chains were provided.`);
}
if (getOmnichainIndexingStatus(chains) === OmnichainIndexingStatusIds.Unstarted) {
const earliestStartBlockTimestamps = chains.map((chain) => chain.config.startBlock.timestamp);
return Math.min(...earliestStartBlockTimestamps) - 1;
}
const latestIndexedBlockTimestamps = chains.filter((chain) => chain.chainStatus !== ChainIndexingStatusIds.Queued).map((chain) => chain.latestIndexedBlock.timestamp);
if (latestIndexedBlockTimestamps.length < 1) {
throw new Error("latestIndexedBlockTimestamps array must include at least one element");
}
return Math.max(...latestIndexedBlockTimestamps);
}
function createIndexingConfig(startBlock, endBlock) {
if (endBlock) {
return {
configType: ChainIndexingConfigTypeIds.Definite,
startBlock,
endBlock
};
}
return {
configType: ChainIndexingConfigTypeIds.Indefinite,
startBlock
};
}
function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotUnstarted(chains) {
return chains.every((chain) => chain.chainStatus === ChainIndexingStatusIds.Queued);
}
function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotBackfill(chains) {
const atLeastOneChainInTargetStatus = chains.some(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill
);
const otherChainsHaveValidStatuses = chains.every(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Queued || chain.chainStatus === ChainIndexingStatusIds.Backfill || chain.chainStatus === ChainIndexingStatusIds.Completed
);
return atLeastOneChainInTargetStatus && otherChainsHaveValidStatuses;
}
function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotCompleted(chains) {
const allChainsHaveValidStatuses = chains.every(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Completed
);
return allChainsHaveValidStatuses;
}
function checkChainIndexingStatusSnapshotsForOmnichainStatusSnapshotFollowing(chains) {
const allChainsHaveValidStatuses = chains.some(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Following
);
return allChainsHaveValidStatuses;
}
function sortChainStatusesByStartBlockAsc(chains) {
chains.sort(
([, chainA], [, chainB]) => chainA.config.startBlock.timestamp - chainB.config.startBlock.timestamp
);
return chains;
}
function getLatestIndexedBlockRef(indexingStatus, chainId) {
const chainIndexingStatus = indexingStatus.omnichainSnapshot.chains.get(chainId);
if (chainIndexingStatus === void 0) {
return null;
}
if (chainIndexingStatus.chainStatus === ChainIndexingStatusIds.Queued) {
return null;
}
return chainIndexingStatus.latestIndexedBlock;
}
// src/ensindexer/indexing-status/validations.ts
function invariant_chainSnapshotQueuedBlocks(ctx) {
const { config } = ctx.value;
if (config.configType === ChainIndexingConfigTypeIds.Indefinite) {
return;
}
if (config.endBlock && isBeforeOrEqualTo(config.startBlock, config.endBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`config.startBlock` must be before or same as `config.endBlock`."
});
}
}
function invariant_chainSnapshotBackfillBlocks(ctx) {
const { config, latestIndexedBlock, backfillEndBlock } = ctx.value;
if (isBeforeOrEqualTo(config.startBlock, latestIndexedBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`config.startBlock` must be before or same as `latestIndexedBlock`."
});
}
if (isBeforeOrEqualTo(latestIndexedBlock, backfillEndBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`latestIndexedBlock` must be before or same as `backfillEndBlock`."
});
}
if (config.configType === ChainIndexingConfigTypeIds.Indefinite) {
return;
}
if (config.endBlock && isEqualTo(backfillEndBlock, config.endBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`backfillEndBlock` must be the same as `config.endBlock`."
});
}
}
function invariant_chainSnapshotCompletedBlocks(ctx) {
const { config, latestIndexedBlock } = ctx.value;
if (isBeforeOrEqualTo(config.startBlock, latestIndexedBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`config.startBlock` must be before or same as `latestIndexedBlock`."
});
}
if (isBeforeOrEqualTo(latestIndexedBlock, config.endBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`latestIndexedBlock` must be before or same as `config.endBlock`."
});
}
}
function invariant_chainSnapshotFollowingBlocks(ctx) {
const { config, latestIndexedBlock, latestKnownBlock } = ctx.value;
if (isBeforeOrEqualTo(config.startBlock, latestIndexedBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`config.startBlock` must be before or same as `latestIndexedBlock`."
});
}
if (isBeforeOrEqualTo(latestIndexedBlock, latestKnownBlock) === false) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "`latestIndexedBlock` must be before or same as `latestKnownBlock`."
});
}
}
function invariant_omnichainSnapshotStatusIsConsistentWithChainSnapshot(ctx) {
const snapshot = ctx.value;
const chains = Array.from(snapshot.chains.values());
const expectedOmnichainStatus = getOmnichainIndexingStatus(chains);
const actualOmnichainStatus = snapshot.omnichainStatus;
if (expectedOmnichainStatus !== actualOmnichainStatus) {
ctx.issues.push({
code: "custom",
input: snapshot,
message: `'${actualOmnichainStatus}' is an invalid omnichainStatus. Expected '${expectedOmnichainStatus}' based on the statuses of individual chains.`
});
}
}
function invariant_omnichainIndexingCursorLowerThanEarliestStartBlockAcrossQueuedChains(ctx) {
const snapshot = ctx.value;
const queuedChains = Array.from(snapshot.chains.values()).filter(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Queued
);
if (queuedChains.length === 0) {
return;
}
const queuedChainStartBlocks = queuedChains.map((chain) => chain.config.startBlock.timestamp);
const queuedChainEarliestStartBlock = Math.min(...queuedChainStartBlocks);
if (snapshot.omnichainIndexingCursor >= queuedChainEarliestStartBlock) {
ctx.issues.push({
code: "custom",
input: snapshot,
message: "`omnichainIndexingCursor` must be lower than the earliest start block across all queued chains."
});
}
}
function invariant_omnichainIndexingCursorLowerThanOrEqualToLatestBackfillEndBlockAcrossBackfillChains(ctx) {
const snapshot = ctx.value;
const backfillChains = Array.from(snapshot.chains.values()).filter(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill
);
if (backfillChains.length === 0) {
return;
}
const backfillEndBlocks = backfillChains.map((chain) => chain.backfillEndBlock.timestamp);
const highestBackfillEndBlock = Math.max(...backfillEndBlocks);
if (snapshot.omnichainIndexingCursor > highestBackfillEndBlock) {
ctx.issues.push({
code: "custom",
input: snapshot,
message: "`omnichainIndexingCursor` must be lower than or equal to the highest `backfillEndBlock` across all backfill chains."
});
}
}
function invariant_omnichainIndexingCursorIsEqualToHighestLatestIndexedBlockAcrossIndexedChain(ctx) {
const snapshot = ctx.value;
const indexedChains = Array.from(snapshot.chains.values()).filter(
(chain) => chain.chainStatus === ChainIndexingStatusIds.Backfill || chain.chainStatus === ChainIndexingStatusIds.Completed || chain.chainStatus === ChainIndexingStatusIds.Following
);
if (indexedChains.length === 0) {
return;
}
const indexedChainLatestIndexedBlocks = indexedChains.map(
(chain) => chain.latestIndexedBlock.timestamp
);
const indexedChainHighestLatestIndexedBlock = Math.max(...indexedChainLatestIndexedBlocks);
if (snapshot.omnichainIndexingCursor !== indexedChainHighestLatestIndexedBlock) {
ctx.issues.push({
code: "custom",
input: snapshot,
message: "`omnichainIndexingCursor` must be same as the highest `latestIndexedBlock` across all indexed chains."
});
}
}
function invariant_omnichainSnapshotUnstartedHasValidChains(ctx) {
const chains = ctx.value;
const hasValidChains = checkChainIndexingStatusSnapshotsForOmnicha