@ensnode/ensnode-sdk
Version:
A utility library for interacting with ENSNode and ENS data
1,366 lines (1,326 loc) • 181 kB
TypeScript
import z$1, { z } from 'zod/v4';
import { ENSNamespaceId, DatasourceName } from '@ensnode/datasources';
export { ENSNamespaceId, ENSNamespaceIds, getENSRootChainId } from '@ensnode/datasources';
import { Hex, Address, ByteArray, Hash } from 'viem';
import { CoinType, EvmCoinType } from '@ensdomains/address-encoder';
export { CoinType, EvmCoinType } from '@ensdomains/address-encoder';
import { UnixTimestamp as UnixTimestamp$1, EncodedReferrer, ReferrerLeaderboardPageParams, ReferrerLeaderboardPage, ReferrerDetail } from '@namehash/ens-referrals';
export { EncodedReferrer, ZERO_ENCODED_REFERRER, decodeEncodedReferrer } from '@namehash/ens-referrals';
/**
* The ETH coinType.
*
* @see https://docs.ens.domains/ensip/9
*/
declare const ETH_COIN_TYPE: CoinType;
/**
* The 'default' chainId corresponding to the below {@link DEFAULT_EVM_COIN_TYPE} in the context of
* ENSIP-19.
*
* @see https://docs.ens.domains/ensip/19
*/
declare const DEFAULT_EVM_CHAIN_ID = 0;
/**
* ENSIP-19 EVM CoinType representing the 'default' coinType for EVM chains in ENS.
*
* @see https://docs.ens.domains/ensip/19/#reverse-resolution
*/
declare const DEFAULT_EVM_COIN_TYPE: EvmCoinType;
/**
* Converts a CoinType to an EVM Chain Id.
*
* NOTE: for whatever reason @ensdomains/address-encoder#coinTypeToEvmChainId doesn't handle the
* mainnet case so we implement that here
*
* @see https://docs.ens.domains/ensip/11/
*/
declare const coinTypeToEvmChainId: (coinType: CoinType) => ChainId;
/**
* Converts an EVM Chain Id to a CoinType.
*
* NOTE: for whatever reason @ensdomains/address-encoder#evmChainIdToCoinType doesn't handle the
* mainnet case so we implement that here
*/
declare const evmChainIdToCoinType: (chainId: ChainId) => CoinType;
/**
* Converts a bigint value representing a CoinType into a valid CoinType.
*
* This is useful when onchain events emit coinTypes as bigint but we want to constrain them to
* the CoinType type.
*
* @throws if `value` is too large to fit in Number.MAX_SAFE_INTEGER
*/
declare const bigintToCoinType: (value: bigint) => CoinType;
/**
* A hash value that uniquely identifies a single ENS name.
* Result of `namehash` function as specified in ENSIP-1.
*
* @example
* ```
* namehash("vitalik.eth") === "0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835"
* ```
* @see https://docs.ens.domains/ensip/1#namehash-algorithm
* @see https://ensnode.io/docs/reference/terminology#name-node-namehash
*/
type Node = Hex;
/**
* An ENS Name that may or may not be normalized.
*
* @example vitalik.eth
* @see https://ensnode.io/docs/reference/terminology#name-node-namehash
* @see https://docs.ens.domains/ensip/15
*/
type Name = string;
/**
* A Normalized Name is an ENS Name that is guaranteed to be normalized.
*
* @example vitalik.eth
* @see https://ensnode.io/docs/reference/terminology#name-node-namehash
* @see https://docs.ens.domains/ensip/15
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type NormalizedName = Name & {
__brand: "NormalizedName";
};
/**
* A LabelHash is the result of the labelhash function (which is just keccak256) on a Label.
*
* @example
* ```
* labelhash('vitalik') === '0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc'
* ```
*
* @see https://docs.ens.domains/terminology#labelhash
* @see https://ensnode.io/docs/reference/terminology#labels-labelhashes-labelhash-function
*/
type LabelHash = Hex;
/**
* A Label is a single part of an ENS Name.
*
* @example vitalik
*
* @see https://docs.ens.domains/terminology#label
* @see https://ensnode.io/docs/reference/terminology#labels-labelhashes-labelhash-function
*/
type Label = string;
/**
* An EncodedLabelHash is a specially formatted (unnormalized) Label formatted
* as a non-0x prefixed 32-byte hex string enclosed in square brackets.
*
* Care should be taken to distinguish Label values formatted as an
* EncodedLabelHash as either a LiteralLabel or an InterpretedLabel:
* - If a LiteralLabel is formatted as an EncodedLabelHash it does NOT
* symbolically represent the encoding of a LabelHash literal.
* - If an InterpretedLabel is formatted as an EncodedLabelHash it should be
* interpreted as encoding a LabelHash literal.
*
* An InterpretedLabel may be formatted as an EncodedLabelHash if the related
* LiteralLabel is:
* - not a normalized label
* - is an unknown value that could not be healed.
* - is too long for DNS-Encoding in contexts where DNS-Encoding was required.
*
* @example [af2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc]
*
* @see https://ensnode.io/docs/reference/terminology#encoded-labelhash
*/
type EncodedLabelHash = `[${string}]`;
/**
* A Literal Label is a Label as it literally appears onchain, without any interpretation
* or normalization processing. It may be an unnormalized label for reasons including:
* - being an empty label,
* - containing '.' characters,
* - being formatted as an EncodedLabelHash (which are not normalizable). Note that
* when LiteralLabel are formatted as an EncodedLabelHash they do NOT symbolically
* represent the encoding of a LabelHash literal, or
* - containing other unnormalized characters such as null bytes or other characters
* not suitable for display.
*
*
* @see https://ensnode.io/docs/reference/terminology#literal-label
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type LiteralLabel = Label & {
__brand: "LiteralLabel";
};
/**
* An Interpreted Label is a Label that is either:
* a) a Normalized Label, or
* b) an Unnormalizable Label exclusively for the reason that it is formatted
* as an Encoded LabelHash that should be interpreted as encoding a
* LabelHash literal, where the encoded LabelHash literal is the `labelhash`
* of the related LiteralLabel.
*
* @see https://ensnode.io/docs/reference/terminology#interpreted-label
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type InterpretedLabel = Label & {
__brand: "InterpretedLabel";
};
/**
* A Literal Name is a Name as it literally appears onchain, composed of 0 or more Literal Labels
* joined by dots. It may be an unnormalized name for reasons including:
* - containing empty labels,
* - containing LiteralLabel values formatted as an EncodedLabelHash (which are
* not normalizable)). Note that when LiteralLabel values are formatted as an
* EncodedLabelHash they do NOT symbolically represent the encoding of a
* LabelHash literal, or
* - containing other unnormalized characters such as null bytes or other characters
* not suitable for display.
*
* @see https://ensnode.io/docs/reference/terminology#literal-name
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type LiteralName = Name & {
__brand: "LiteralName";
};
/**
* An Interpreted Name is a Name that is entirely composed of 0 or more Interpreted Labels.
*
* That is, it is either:
* a) a Normalized Name, or
* b) an Unnormalizable Name exclusively for the reason that it contains 1 or
* more labels formatted as Encoded LabelHashes that should be interpreted
* as encoding a LabelHash literal, where the encoded LabelHash literal is
* the `labelhash` of the related LiteralLabel.
*
* @see https://ensnode.io/docs/reference/terminology#interpreted-name
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type InterpretedName = Name & {
__brand: "InterpretedName";
};
/**
* A Subgraph Interpreted Label is a Literal Label that is either:
* a) (if subgraph-indexable): a Literal Label, of unknown normalization status, guaranteed to not
* contain any of the subgraph-unindexable UTF-8 characters (and therefore guaranteed not to be
* an Encoded LabelHash), or
* b) (if subgraph-unindexable): an Encoded LabelHash.
*
* @see https://ensnode.io/docs/reference/terminology#subgraph-interpreted-label
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type SubgraphInterpretedLabel = Label & {
__brand: "SubgraphInterpretedLabel";
};
/**
* A Subgraph Interpreted Name is a name exclusively composed of 0 or more Subgraph Interpreted Labels.
*
* @see https://ensnode.io/docs/reference/terminology#subgraph-interpreted-name
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type SubgraphInterpretedName = Name & {
__brand: "SubgraphInterpretedName";
};
/**
* A DNS-Encoded Name as a hex string, representing the binary DNS wire format encoding
* of a domain name. Used in ENS contracts for efficient name storage and transmission.
* Each label is prefixed with a length byte, and the entire sequence is null-terminated.
*
* @example "0x076578616d706c650365746800" represents "example.eth"
*
* @see https://docs.ens.domains/resolution/names/#dns-encoding
* @see https://github.com/ensdomains/ens-contracts/blob/staging/contracts/utils/NameCoder.sol
*
* DNS Packet Format for Domain Names:
* - Domain names are encoded as a sequence of 0 or more labels
* - Each label begins with a length byte (1 byte) indicating how many bytes follow for that label
* Note how this constrains each label in DNS encoded names to a max byte length of 255 bytes.
* - The bytes after the length byte represent the label, as a UTF-8 byte array
* - Labels are concatenated with no separators
* - The sequence ends with a null byte (0x00)
*
* Example: "example.eth" is encoded as:
* [0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'e', 't', 'h', 0x00]
* Where 0x07 is the length of "example", 0x03 is the length of "eth", and 0x00 marks the end
*
* Example: "" (empty string, i.e. root node) is encoded as:
* [0x00]
*
* Example: "👩🏼❤💋👨🏼.eth" (multi-byte unicode character) is encoded as:
* [0x20, 240, 159, 145, 169, 240, 159, 143, 188, 226, 128, 141, 226, 157, 164, 226,
* 128, 141, 240, 159, 146, 139, 226, 128, 141, 240, 159, 145, 168, 240, 159, 143,
* 188, 3, 'e', 't', 'h', 0x00]
*
* A DNS-Encoded Name Packet may be malformed if it does not exactly follow that specification.
* Possible reasons a DNS-Encoded Name may be malfomed include:
* - 'empty' packet
* - e.g. []
* ^-- that's empty!
* - 'length' byte overflowing packet byte length
* - e.g. [0x06, 'e', 't', 'h', 0x00]
* ^-- length overflows available bytes!
* - 'junk' at the end of the dns-encoded
* - e.g. [0x03, 'e', 't', 'h', 0x00, 0x01]
* ^-- junk!
*
* @dev This type is _structurally_ typed to aid Event Argument Typing — consumers should further
* cast the type of the event argument to a _nominally_ typed DNSEncodedName like {@link DNSEncodedLiteralName}
* or {@link DNSEncodedPartiallyInterpretedName} depending on the context.
*/
type DNSEncodedName = Hex;
/**
* A DNSEncodedName that encodes a name containing 0 or more {@link LiteralLabel}s.
*
* In a DNSEncodedLiteralName, all labels are Literal Labels, including any Encoded-LabelHash-looking
* labels. Any Encoded-LabelHash-looking Literal Label values, when interpreted, will be formatted as
* the `labelhash` of the Literal Label value.
*
* The NameWrapper contract emits DNSEncodedLiteralNames:
* @see https://github.com/ensdomains/ens-contracts/blob/staging/contracts/utils/BytesUtils_LEGACY.sol
*
* The ThreeDNSToken contract emits DNSEncodedLiteralNames:
* @see https://github.com/3dns-xyz/contracts/blob/44937318ae26cc036982e8c6a496cd82ebdc2b12/src/regcontrol/libraries/BytesUtils.sol
*
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type DNSEncodedLiteralName = DNSEncodedName & {
__brand: "DNSEncodedLiteralName";
};
/**
* A DNSEncodedName that encodes a name consisting of 0 or more labels that are either:
* a) Literal Labels, or
* b) Encoded LabelHashes, which are already an Interpreted Label.
*
* In a DNSEncodedPartiallyInterpretedName, any Encoded-LabelHash-looking decoded Labels (i.e. ones
* that match the regex /^\[[\da-f]{64}\]$/) represent an Encoded LabelHash. When decoding a
* DNSEncodedPartiallyInterpretedName, these labels are already considered Interpreted.
*
* NOTE: This type is unused in ENSv1, but its usage is anticipated in ENSv2 due to Encoded
* LabelHash support in the ENSv2 implementation of the NameCoder contract.
*
* @see https://github.com/ensdomains/ens-contracts/blob/staging/contracts/utils/NameCoder.sol
*
* @dev nominally typed to enforce usage & enhance codebase clarity
*/
type DNSEncodedPartiallyInterpretedName = DNSEncodedName & {
__brand: "DNSEncodedPartiallyInterpretedName";
};
declare const ROOT_NODE: Node;
declare const ETH_NODE: Node;
declare const BASENAMES_NODE: Node;
declare const LINEANAMES_NODE: Node;
declare const ADDR_REVERSE_NODE: Node;
/**
* Decodes a DNS-Encoded name consisting of Literal Labels into an ordered list of Literal Labels.
*
* For discussion on DNS-Encoding, see the {@link DNSEncodedName} and {@link DNSEncodedLiteralName} types.
*
* Due to the constraints of DNS-Encoding, there is an additional guarantee that each Literal Label
* in the resulting list is guaranteed to have a maximum byte length of 255.
*
* @param packet a hex string that encodes a DNSEncodedLiteralName
* @returns A list of the LiteralLabels contained in packet
* @throws If the packet is malformed
* @dev This is just `decodeDNSEncodedName` with semantic input/output
*/
declare function decodeDNSEncodedLiteralName(packet: DNSEncodedLiteralName): LiteralLabel[];
/**
* Decodes a DNS-Encoded Name into an ordered list of string segments.
*
* For discussion on DNS-Encoding, see the {@link DNSEncodedName} type.
*
* Due to the constraints of DNS-Encoding, there is an additional guarantee that each segment
* in the resulting list is guaranteed to have a maximum byte length of 255.
*
* @param packet a hex string that encodes a DNSEncodedName
* @returns A UTF-8 string array of the segments contained in packet
* @throws If the packet is malformed
* @dev This is the generic implementation of DNS-Encoded Name Decoding
*/
declare function decodeDNSEncodedName(packet: DNSEncodedName): string[];
/**
* Formats a LabelHash as an Encoded LabelHash.
*
* @see https://ensnode.io/docs/reference/terminology#encoded-labelhash
*
* @param labelHash - A 32-byte lowercase hash string starting with '0x'
* @returns The encoded label hash in format `[hash_without_0x_prefix]`
*/
declare const encodeLabelHash: (labelHash: LabelHash) => EncodedLabelHash;
/**
* Checks if the input value is an {@link EncodedLabelHash}.
*/
declare function isEncodedLabelHash(maybeEncodedLabelHash: string): maybeEncodedLabelHash is EncodedLabelHash;
/**
* Determines whether the Name is normalized.
*
* @param name - The Name to check for normalization
* @returns True if the name is normalized according to ENS normalization rules, false otherwise
*/
declare function isNormalizedName(name: Name): name is NormalizedName;
/**
* Determines whether the Label is normalized.
*
* @param label - The Label to check for normalization
* @returns True if the label is normalized according to ENS normalization rules, false otherwise
*/
declare function isNormalizedLabel(label: Label): boolean;
/**
* Checks if the input is a {@link LabelHash}.
*
* @see https://ensnode.io/docs/reference/terminology#label-processing-and-classification
*/
declare function isLabelHash(maybeLabelHash: string): maybeLabelHash is LabelHash;
/**
* Name for the ENS Root
*/
declare const ENS_ROOT: Name;
/**
* Constructs a name hierarchy from a given NormalizedName.
*
* @example
* ```
* getNameHierarchy("sub.example.eth") -> ["sub.example.eth", "example.eth", "eth"]
* ```
*
* @dev by restricting the input type to NormalizedName we guarantee that we can split and join
* on '.' and receive NormalizedNames as a result
*/
declare const getNameHierarchy: (name: NormalizedName) => NormalizedName[];
/**
* Get FQDN of parent for a name.
*/
declare const getParentNameFQDN: (name: Name) => Name;
/**
* Beautifies a name by converting each normalized label in the provided name to
* its "beautified" form. Labels that are not normalized retain their original value.
*
* Invariants:
* - The number of labels in the returned name is the same as the number of labels in the input name.
* - The order of the labels in the returned name is the same as the order of the labels in the input name.
* - If a label in the input is normalized, it is returned in its "beautified" form.
* - If a label in the input name is not normalized, it is returned without modification.
* - Therefore, the result of ens_normalize(beautifyName(name)) is the same as the result of ens_normalize(name).
*
* The "beautified form" of a normalized label converts special sequences of
* emojis and other special characters to their "beautified" equivalents. All
* such conversions transform X -> Y where Y is normalizable and normalizes back to X.
* Ex: '1⃣2⃣' (normalized) to '1️⃣2️⃣' (normalizable but not normalized).
* Ex: 'ξethereum' (normalized) to 'Ξethereum' (normalizable, but not normalized).
* Ex: 'abc' (normalized) to 'abc' (also normalized, no conversion).
* Ex: 'ABC' (normalizable but not normalized) to 'ABC' (no conversion).
* Ex: 'invalid|label' (not normalizable) to 'invalid|label' (no conversion).
* Ex: '' (unnormalized as a label) to '' (no conversion).
*
* @param name - The name to beautify.
* @returns The beautified name.
*/
declare const beautifyName: (name: Name) => Name;
/**
* Parse the address and coinType out of an ENSIP-19 reverse name.
*/
declare function parseReverseName(name: Name): {
address: Address;
coinType: CoinType;
} | null;
/**
* Gets the Label used for the reverse names of subnames as per ENSIP-11 & ENSIP-19.
*
* @see https://docs.ens.domains/ensip/19/#reverse-resolution
*/
declare const addrReverseLabel: (address: Address) => LiteralLabel;
/**
* Converts `coinType` to prefix-free hex string.
*
* @see https://docs.ens.domains/ensip/19
*/
declare const coinTypeReverseLabel: (coinType: CoinType) => Label;
/**
* Gets the reverse name for an address according to ENSIP-11 & ENSIP-19.
*
* @see https://docs.ens.domains/ensip/11#specification
* @see https://docs.ens.domains/ensip/19#specification
*
* @param address - The address to get the reverse name for
* @param coinType - The coin type to use for the reverse name
* @returns The reverse name for the address
*
* @example
* ```ts
* reverseName("0x1234", BigInt(ETH_COIN_TYPE)) // "1234.addr.reverse"
* reverseName("0x1234", BigInt(0x80000000)) // "1234.default.reverse"
* reverseName("0x1234", BigInt(0x5678)) // "1234.5678.reverse"
* ```
*/
declare function reverseName(address: Address, coinType: CoinType): Name;
/**
* Implements one step of the namehash algorithm, combining `labelHash` with `node` to produce
* the `node` of a given subdomain. Note that the order of the arguments is 'reversed' (as compared to
* the actual concatenation) in order to improve readability (i.e. read as [labelHash].[node]).
*/
declare const makeSubdomainNode: (labelHash: LabelHash, node: Node) => Node;
/**
* Encodes a uint256 bigint as hex string sized to 32 bytes.
* Uses include, in the context of ENS, decoding the uint256-encoded tokenId of NFT-issuing contracts
* into Node or LabelHash, which is a common behavior in the ENS ecosystem.
* (see NameWrapper, ETHRegistrarController)
*/
declare const uint256ToHex32: (num: bigint) => Hex;
/**
* Chain ID
*
* Represents a unique identifier for a chain.
* Guaranteed to be a positive integer.
**/
type ChainId = number;
/**
* Defaultable Chain ID
*
* Represents a unique identifier for a chain, or
* the default chain as defined by ENSIP-19.
*
* @see https://docs.ens.domains/ensip/19/#annex-supported-chains
*
* Guaranteed to be a non-negative integer.
**/
type DefaultableChainId = typeof DEFAULT_EVM_CHAIN_ID | ChainId;
/**
* Represents an account (contract or EOA) at `address` on chain `chainId`.
*
* @see https://chainagnostic.org/CAIPs/caip-10
*/
interface AccountId {
chainId: ChainId;
address: Address;
}
/**
* Block Number
*
* Guaranteed to be a non-negative integer.
*/
type BlockNumber = number;
/**
* Datetime value
*/
type Datetime = Date;
/**
* Unix timestamp value
*
* Represents the number of seconds that have elapsed
* since January 1, 1970 (midnight UTC/GMT).
*
* Guaranteed to be an integer. May be zero or negative to represent a time at or
* before Jan 1, 1970.
*/
type UnixTimestamp = number;
/**
* Represents a URL that is used for RPC endpoints.
*/
type RpcUrl = URL;
/**
* BlockRef
*
* Describes a block.
*
* We use parameter types to maintain fields layout and documentation across
* the domain model and its serialized counterpart.
*/
interface BlockRef {
/** Block number (height) */
number: BlockNumber;
/** Block timestamp */
timestamp: UnixTimestamp;
}
/**
* Block range
*
* Represents a range of blocks
*/
interface Blockrange<BlockType = BlockNumber> {
/** Start block number */
startBlock?: BlockType;
/** End block number */
endBlock?: BlockType;
}
/**
* Duration
*
* Representing a duration in seconds.
*
* Guaranteed to be a non-negative integer.
*/
type Duration = number;
/**
* A utility type that makes all properties of a type optional recursively,
* including nested objects and arrays.
*
* @example
* ```typescript
* type Config = {
* a: string;
* b: {
* x: number;
* y: { z: boolean };
* };
* c: { id: string }[];
* }
*
* type PartialConfig = DeepPartial<Config>;
* // Results in:
* // {
* // a?: string;
* // b?: {
* // x?: number;
* // y?: { z?: boolean };
* // };
* // c?: { id?: string }[];
* // }
*
* // Usage:
* const update: PartialConfig = { b: { y: { z: true } } };
* ```
*/
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial<U>[] : T[P] extends object ? DeepPartial<T[P]> : T[P];
};
/**
* Determines where the provided AccountId values represent the same address on the same chain.
*/
declare const accountIdEqual: (a: AccountId, b: AccountId) => boolean;
/**
* Converts an EVM address to its lowercase representation.
*
* @param address - EVM address to convert.
* @returns The lowercase representation of the EVM address.
*/
declare function asLowerCaseAddress(address: Address): Address;
/**
* Cache that maps from string -> ValueType.
*/
interface Cache<KeyType extends string, ValueType> {
/**
* Store a value in the cache with the given key.
*
* @param key Cache key
* @param value Value to store
*/
set(key: KeyType, value: ValueType): void;
/**
* Retrieve a value from the cache with the given key.
*
* @param key Cache key
* @returns The cached value if it exists, otherwise undefined
*/
get(key: KeyType): ValueType | undefined;
/**
* Clear the cache.
*/
clear(): void;
/**
* The current number of items in the cache. Always a non-negative integer.
*/
get size(): number;
/**
* The maximum number of items in the cache. Always a non-negative integer that is >= size().
*/
get capacity(): number;
}
/**
* Cache that maps from string -> ValueType with a LRU (least recently used) eviction policy.
*
* `get` and `set` are O(1) operations.
*
* @link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
*/
declare class LruCache<KeyType extends string, ValueType> implements Cache<KeyType, ValueType> {
private readonly _cache;
private readonly _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: number);
set(key: string, value: ValueType): void;
get(key: string): ValueType | undefined;
clear(): void;
get size(): number;
get capacity(): number;
}
interface SWRCacheOptions<ValueType> {
/**
* The async function generating a value of `ValueType` to wrap with SWR caching. It may throw an
* Error type.
*/
fn: () => Promise<ValueType>;
/**
* Time-to-live duration of a cached result in seconds. After this duration:
* - the currently cached result is considered "stale" but is still retained in the cache
* until successfully replaced.
* - Each time the cache is read, if the cached result is "stale" and no background
* revalidation attempt is already in progress, a new background revalidation
* attempt will be made.
*/
ttl: Duration;
/**
* Optional time-to-proactively-revalidate duration in seconds. After a cached result is
* initialized, and this duration has passed, attempts to asynchronously revalidate
* the cached result will be proactively made in the background on this interval.
*/
proactiveRevalidationInterval?: Duration;
/**
* Optional proactive initialization. Defaults to `false`.
*
* If `true`: The SWR cache will proactively initialize itself.
* If `false`: The SWR cache will lazily wait to initialize itself until the first read.
*/
proactivelyInitialize?: boolean;
}
/**
* Stale-While-Revalidate (SWR) cache for async functions.
*
* This caching strategy serves cached data immediately (even if stale) while
* asynchronously revalidating the cache in the background. This provides:
* - Sub-millisecond response times (after first fetch)
* - Always available data (serves stale data during revalidation)
* - Automatic background updates via configurable intervals
*
* @example
* ```typescript
* const cache = new SWRCache({
* fn: async () => fetch('/api/data').then(r => r.json()),
* ttl: 60, // 1 minute TTL
* proactiveRevalidationInterval: 300 // proactively revalidate every 5 minutes
* });
*
* // Returns cached data or waits for initial fetch
* const data = await cache.read();
*
* if (data instanceof Error) { ... }
* ```
*
* @link https://web.dev/stale-while-revalidate/
* @link https://datatracker.ietf.org/doc/html/rfc5861
*/
declare class SWRCache<ValueType> {
private readonly options;
private cache;
private inProgressRevalidate;
private backgroundInterval;
constructor(options: SWRCacheOptions<ValueType>);
private revalidate;
/**
* 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.
*/
read(): Promise<ValueType | Error>;
/**
* Destroys the background revalidation interval, if exists.
*/
destroy(): void;
}
/**
* Cache that maps from string -> ValueType with TTL (time-to-live) expiration.
*
* Items are automatically removed when they expire.
*/
declare class TtlCache<KeyType extends string, ValueType> implements Cache<KeyType, ValueType> {
private readonly _cache;
private readonly _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: Duration);
private _cleanup;
set(key: string, value: ValueType): void;
get(key: string): ValueType | undefined;
clear(): void;
get size(): number;
get capacity(): number;
has(key: string): boolean;
delete(key: string): boolean;
}
/**
* Filter out duplicates.
*/
declare const uniq: <T>(arr: T[]) => T[];
/**
* Identifiers for supported currencies.
*
* TODO: Add support for WETH
*/
declare const CurrencyIds: {
readonly ETH: "ETH";
readonly USDC: "USDC";
readonly DAI: "DAI";
};
type CurrencyId = (typeof CurrencyIds)[keyof typeof CurrencyIds];
/**
* The amount of the currency in the smallest unit of the currency
* (see {@link CurrencyInfo.decimals} for the currency).
*
* Guaranteed to be non-negative.
*/
type CurrencyAmount = bigint;
/**
* Serialized representation of {@link CurrencyAmount}.
*/
type SerializedCurrencyAmount = string;
interface PriceEth {
currency: typeof CurrencyIds.ETH;
amount: CurrencyAmount;
}
interface PriceDai {
currency: typeof CurrencyIds.DAI;
amount: CurrencyAmount;
}
interface PriceUsdc {
currency: typeof CurrencyIds.USDC;
amount: CurrencyAmount;
}
type Price = PriceEth | PriceDai | PriceUsdc;
/**
* Serialized representation of {@link PriceEth}.
*/
interface SerializedPriceEth extends Omit<PriceEth, "amount"> {
amount: SerializedCurrencyAmount;
}
/**
* Serialized representation of {@link PriceDai}.
*/
interface SerializedPriceDai extends Omit<PriceDai, "amount"> {
amount: SerializedCurrencyAmount;
}
/**
* Serialized representation of {@link PriceUsdc}.
*/
interface SerializedPriceUsdc extends Omit<PriceUsdc, "amount"> {
amount: SerializedCurrencyAmount;
}
/**
* Serialized representation of {@link Price}.
*/
type SerializedPrice = SerializedPriceEth | SerializedPriceDai | SerializedPriceUsdc;
interface CurrencyInfo {
id: CurrencyId;
name: string;
decimals: number;
}
/**
* Get currency info for a provided currency.
*/
declare function getCurrencyInfo(currencyId: CurrencyId): CurrencyInfo;
/**
* Create price in ETH for given amount.
*/
declare function priceEth(amount: Price["amount"]): PriceEth;
/**
* Create price in USDC for given amount.
*/
declare function priceUsdc(amount: Price["amount"]): PriceUsdc;
/**
* Create price in DAI for given amount.
*/
declare function priceDai(amount: Price["amount"]): PriceDai;
/**
* Check if two prices have the same currency.
*/
declare function isPriceCurrencyEqual(priceA: Price, priceB: Price): boolean;
/**
* Check if two {@link Price} values have the same currency and amount.
*/
declare function isPriceEqual(priceA: Price, priceB: Price): boolean;
/**
* Add prices
*
* @param prices at least two {@link Price} values to be added together.
* @returns total of all prices.
* @throws if not all prices have the same currency.
*/
declare function addPrices<const PriceType extends Price = Price>(...prices: [PriceType, PriceType, ...PriceType[]]): PriceType;
/**
* Gets the AccountId for the contract in the specified namespace, datasource, and
* contract name, or undefined if it is not defined or is not a single AccountId.
*
* This is useful when you want to retrieve the AccountId for a contract by its name
* where it may or may not actually be defined for the given namespace and datasource.
*
* @param namespaceId - The ENSNamespace identifier (e.g. 'mainnet', 'sepolia', 'holesky',
* 'ens-test-env')
* @param datasourceName - The name of the Datasource to search for contractName in
* @param contractName - The name of the contract to retrieve
* @returns The AccountId of the contract with the given namespace, datasource,
* and contract name, or undefined if it is not found or is not a single AccountId
*/
declare const maybeGetDatasourceContract: (namespaceId: ENSNamespaceId, datasourceName: DatasourceName, contractName: string) => AccountId | undefined;
/**
* Gets the AccountId for the contract in the specified namespace, datasource, and
* contract name, or throws an error if it is not defined or is not a single AccountId.
*
* @param namespaceId - The ENSNamespace identifier (e.g. 'mainnet', 'sepolia', 'holesky',
* 'ens-test-env')
* @param datasourceName - The name of the Datasource to search for contractName in
* @param contractName - The name of the contract to retrieve
* @returns The AccountId of the contract with the given namespace, datasource,
* and contract name
* @throws Error if the contract is not found or is not a single AccountId
*/
declare const getDatasourceContract: (namespaceId: ENSNamespaceId, datasourceName: DatasourceName, contractName: string) => AccountId;
/**
* Duration between two moments in time.
*/
declare function durationBetween(start: UnixTimestamp, end: UnixTimestamp): Duration;
/**
* Add a duration to a timestamp.
*/
declare function addDuration(timestamp: UnixTimestamp, duration: Duration): UnixTimestamp;
/**
* Serialized representation of {@link ChainId}.
**/
type ChainIdString = string;
/**
* Datetime value following the ISO 8601 standard.
*
* @see https://www.iso.org/iso-8601-date-and-time-format.html
*/
type DatetimeISO8601 = string;
/**
* Serialized representation of a {@link URL}.
*/
type UrlString = string;
/**
* String representation of {@link AccountId}.
*
* Formatted as a fully lowercase CAIP-10 AccountId.
*
* @see https://chainagnostic.org/CAIPs/caip-10
*/
type AccountIdString = string;
declare function deserializeChainId(maybeChainId: ChainIdString, valueLabel?: string): ChainId;
declare function deserializeDatetime(maybeDatetime: string, valueLabel?: string): Datetime;
declare function deserializeUnixTimestamp(maybeTimestamp: number, valueLabel?: string): number;
declare function deserializeUrl(maybeUrl: UrlString, valueLabel?: string): URL;
declare function deserializeBlockNumber(maybeBlockNumber: number, valueLabel?: string): BlockNumber;
declare function deserializeBlockrange(maybeBlockrange: Partial<Blockrange>, valueLabel?: string): {
startBlock?: number | undefined;
endBlock?: number | undefined;
};
declare function deserializeBlockRef(maybeBlockRef: Partial<BlockRef>, valueLabel?: string): BlockRef;
declare function deserializeDuration(maybeDuration: unknown, valueLabel?: string): Duration;
declare function parseAccountId(maybeAccountId: unknown, valueLabel?: string): AccountId;
/**
* Interprets a Literal Label, producing an Interpreted Label.
*
* @see https://ensnode.io/docs/reference/terminology#literal-label
* @see https://ensnode.io/docs/reference/terminology#interpreted-label
*
* @param label - The Literal Label string to interpret
* @returns The provided label if it is a normalized label, else the EncodedLabelHash of the label
*/
declare function literalLabelToInterpretedLabel(label: LiteralLabel): InterpretedLabel;
/**
* Interprets an ordered list of Literal Labels, producing an Interpreted Name.
*
* Note that it's important that the Literal Labels are provided as an array, otherwise it's
* impossible to differentiate between 'a.label.eth' being ['a.label', 'eth'] or ['a', 'label', 'eth'].
*
* Note that the input is an ordered list of _Literal_ Labels: in this context, any literal label
* that is formatted as an Encoded LabelHash will NOT be interpreted as such. Instead it will be
* interpreted into an Encoded LabelHash that encodes the literal labelhash of the Literal Label.
*
* @param labels An ordered list of 0 or more Literal Labels
* @returns An InterpretedName
*/
declare function literalLabelsToInterpretedName(labels: LiteralLabel[]): InterpretedName;
/**
* Joins the list of Interpreted Labels with '.' to form an Interpreted Name.
*
* @param labels An ordered list of 0 or more Interpreted Labels
* @returns An InterpretedName
*/
declare function interpretedLabelsToInterpretedName(labels: InterpretedLabel[]): InterpretedName;
/**
* Joins the list of Literal Labels with '.' to form a Literal Name.
*
* Note: LiteralLabel values may contain '.' characters, which will be preserved
* in the resulting LiteralName. Therefore, the number of labels in the returned
* LiteralName may be greater than the number of LiteralLabels in the input array.
*
* @param labels An ordered list of 0 or more Literal Labels
* @returns An LiteralName
*/
declare function literalLabelsToLiteralName(labels: LiteralLabel[]): LiteralName;
/**
* Implements the ENS `labelhash` function for Literal Labels.
* @see https://docs.ens.domains/ensip/1
*
* @param label the Literal Label to hash
* @returns the hash of the provided label
* @dev This function is viem/ens#labelhash but without the special-case handling of Encoded LabelHashes.
*/
declare const labelhashLiteralLabel: (label: LiteralLabel) => LabelHash;
declare const hasNullByte: (value: string) => boolean;
declare const stripNullBytes: (value: string) => string;
/**
* Converts a bigint value into a number value.
*
* @throws when value is outside the range of `Number.MIN_SAFE_INTEGER` and
* `Number.MAX_SAFE_INTEGER`.
*/
declare function bigIntToNumber(n: bigint): number;
/**
* Serializes a {@link ChainId} value into its string representation.
*/
declare function serializeChainId(chainId: ChainId): ChainIdString;
/**
* Serializes a {@link Datetime} value into its string representation.
*/
declare function serializeDatetime(datetime: Datetime): DatetimeISO8601;
/**
* Serializes a {@link URL} value into its string representation.
*/
declare function serializeUrl(url: URL): UrlString;
/**
* Serializes a {@link Price} object.
*/
declare function serializePrice(price: Price): SerializedPrice;
/**
* Serializes a {@link PriceEth} object.
*/
declare function serializePriceEth(price: PriceEth): SerializedPriceEth;
/**
* Format {@link AccountId} object as a string.
*
* Formatted as a fully lowercase CAIP-10 AccountId.
*
* @see https://chainagnostic.org/CAIPs/caip-10
*/
declare function formatAccountId(accountId: AccountId): AccountIdString;
declare function isHttpProtocol(url: URL): boolean;
declare function isWebSocketProtocol(url: URL): boolean;
/**
* A label set ID identifies a set of labels that can be used for deterministic healing.
* A label set allows clients to deterministically heal their state against a server,
* ensuring that both are operating on the same version of data.
*
* It is guaranteed to be 1 to 50 characters long and contain only lowercase letters (a-z)
* and hyphens (-).
*/
type LabelSetId = string;
/**
* A label set version identifies a specific version of a label set. It allows clients to
* request data from a specific snapshot in time, ensuring deterministic results.
*
* It is guaranteed to be a non-negative integer.
*/
type LabelSetVersion = number;
/**
* The label set preferences of an ENSRainbow client.
*/
interface EnsRainbowClientLabelSet {
/**
* Optional label set ID that the ENSRainbow server is expected to use. If provided, heal
* operations will validate the ENSRainbow server is using this labelSetId.
* Required if `labelSetVersion` is defined.
*/
labelSetId?: LabelSetId;
/**
* Optional highest label set version of label set id to query. Enables deterministic heal
* results across time even if the ENSRainbow server ingests label sets with greater versions
* than this value. If provided, only labels from label sets with versions less than or equal to this
* value will be returned. If not provided, the server will use the latest available version.
* When `labelSetVersion` is defined, `labelSetId` must also be defined.
*/
labelSetVersion?: LabelSetVersion;
}
/**
* The state of label sets managed by an ENSRainbow server.
*/
interface EnsRainbowServerLabelSet {
/**
* The LabelSetId managed by the ENSRainbow server.
*/
labelSetId: LabelSetId;
/**
* The highest label set version available on the ENSRainbow server for the current
* label set ID. This represents the most recent version of the label set that the
* server has ingested and can provide label healing results for.
*/
highestLabelSetVersion: LabelSetVersion;
}
/**
* A PluginName is a unique id for a 'plugin': we use the notion of
* 'plugins' to describe bundles of indexing logic.
*/
declare enum PluginName {
Subgraph = "subgraph",
Basenames = "basenames",
Lineanames = "lineanames",
ThreeDNS = "threedns",
ProtocolAcceleration = "protocol-acceleration",
Registrars = "registrars",
TokenScope = "tokenscope"
}
/**
* Version info about ENSIndexer and its dependencies.
*/
interface ENSIndexerVersionInfo {
/**
* Node.js runtime version
*
* @see https://nodejs.org/en/about/previous-releases
**/
nodejs: string;
/**
* Ponder framework version
*
* @see https://www.npmjs.com/package/ponder
**/
ponder: string;
/**
* ENSDb service version
*
* Guaranteed to be the same as {@link ENSIndexerVersionInfo.ensIndexer}.
* */
ensDb: string;
/**
* ENSIndexer service version
*
* @see https://ghcr.io/namehash/ensnode/ensindexer
**/
ensIndexer: string;
/**
* ENSRainbow service version
*
* @see https://ghcr.io/namehash/ensnode/ensindexer
**/
ensRainbow: string;
/**
* ENSRainbow schema version
**/
ensRainbowSchema: number;
/**
* ENS Normalize package version
*
* Available on NPM as: `@adraffy/ens-normalize`
*
* @see https://www.npmjs.com/package/@adraffy/ens-normalize
**/
ensNormalize: string;
}
/**
* Complete public configuration object for ENSIndexer.
*
* We use parameter types to maintain fields layout and documentation across
* the domain model and its serialized counterpart.
*/
interface ENSIndexerPublicConfig {
/**
* The ENS namespace that ENSNode operates in the context of.
*
* See {@link ENSNamespaceIds} for available namespace identifiers.
*/
namespace: ENSNamespaceId;
/**
* The "fully pinned" label set reference that ENSIndexer will request ENSRainbow use for deterministic label healing across time. This label set reference is "fully pinned" as it requires both the labelSetId and labelSetVersion fields to be defined.
*/
labelSet: Required<EnsRainbowClientLabelSet>;
/**
* A Postgres database schema name. This instance of ENSIndexer will write
* indexed data to the tables in this schema.
*
* Invariants:
* - Must be a non-empty string that is a valid Postgres database schema
* identifier.
*/
databaseSchemaName: string;
/**
* A set of strings referring to the names of plugins that are active.
*
* For future-proofing, this is a list of strings that may or may
* not be currently valid {@link PluginName} values.
*
* Invariants:
* - A set of strings with at least one value.
*/
plugins: string[];
/**
* Indexed Chain IDs
*
* Includes the {@link ChainId} for each chain being indexed.
*/
indexedChainIds: Set<ChainId>;
/**
* A feature flag to enable/disable ENSIndexer's Subgraph Compatible Indexing Behavior.
*
* If {@link isSubgraphCompatible} is true, indexing behavior will match that of the legacy ENS
* Subgraph.
*
* ENSIndexer will store and return Literal Labels and Literal Names without further interpretation.
* @see https://ensnode.io/docs/reference/terminology#literal-label
* @see https://ensnode.io/docs/reference/terminology#literal-name
*
* If {@link isSubgraphCompatible} is true, the following invariants are true for the ENSIndexerConfig:
* 1. only the 'subgraph' plugin is enabled, and
* 2. the labelSet must be { labelSetId: 'subgraph', labelSetVersion: 0 }
*
* If {@link isSubgraphCompatible} is false, ENSIndexer will additionally:
*
* 1. ENSIndexer will heal all subnames of addr.reverse on the ENS Root Chain.
*
* 2. ENSIndexer will track both the keys and the values of Resolver records.
*
* WARNING: Special care must be taken when interacting with indexed resolver record values. It
* is unsafe to naively assume that indexed resolver record values are equivalent to the
* resolver record values that would be returned through dynamic lookups via the ENS protocol.
* For example, if a resolver implements CCIP-Read, the resolver records may not be
* discoverable through onchain indexing.
*
* 3. Literal Labels and Literal Names encountered by ENSIndexer will be Interpreted.
* @see https://ensnode.io/docs/reference/terminology#interpreted-label
* @see https://ensnode.io/docs/reference/terminology#interpreted-name
*
* That is,
* a) all Labels stored and returned by ENSIndexer will be Interpreted Labels, which are either:
* i. normalized, or
* ii. represented as an Encoded LabelHash of the Literal Label value found onchain, and
* b) all Names stored and returned by ENSIndexer will be Interpreted Names, which are exclusively
* composed of Interpreted Labels.
*/
isSubgraphCompatible: boolean;
/**
* Version info about ENSIndexer.
*/
versionInfo: ENSIndexerVersionInfo;
}
type SerializedIndexedChainIds = Array<ChainId>;
/**
* Serialized representation of {@link ENSIndexerPublicConfig}
*/
interface SerializedENSIndexerPublicConfig extends Omit<ENSIndexerPublicConfig, "indexedChainIds"> {
/**
* Array representation of {@link ENSIndexerPublicConfig.indexedChainIds}.
*/
indexedChainIds: ChainId[];
}
/**
* Serialized representation of {@link ENSIndexerVersionInfo}
*/
type SerializedENSIndexerVersionInfo = ENSIndexerVersionInfo;
/**
* Serialize a {@link ENSIndexerPublicConfig} object.
*/
declare function deserializeENSIndexerPublicConfig(maybeConfig: SerializedENSIndexerPublicConfig, valueLabel?: string): ENSIndexerPublicConfig;
/**
* Determines if the provided `config` results in indexing behavior compatible with the legacy ENS
* Subgraph.
*
* @see https://ensnode.io/docs/reference/subgraph-compatibility/
*/
declare function isSubgraphCompatible(config: Pick<ENSIndexerPublicConfig, "namespace" | "plugins" | "labelSet">): boolean;
/**
* Converts a Labelhash to bytes, with validation
* @param labelHash The Labelhash to convert
* @returns A ByteArray containing the bytes
* @throws Error if `labelHash` is not a valid 32-byte hex string
*/
declare function labelHashToBytes(labelHash: LabelHash): ByteArray;
/**
* Builds a valid LabelSetId from a string.
* @param maybeLabelSetId - The string to validate and convert to a LabelSetId.
* @returns A valid LabelSetId.
* @throws If the input string is not a valid LabelSetId.
*/
declare function buildLabelSetId(maybeLabelSetId: string): LabelSetId;
/**
* Builds a valid LabelSetVersion from a number or string.
* @param maybeLabelSetVersion - The number or string to validate and convert to a LabelSetVersion.
* @returns A valid LabelSetVersion.
* @throws If the input is not a valid LabelSetVersion.
*/
declare function buildLabelSetVersion(maybeLabelSetVersion: number | string): LabelSetVersion;
/**
* Builds an EnsRainbowClientLabelSet.
* @param labelSetId - The label set ID.
* @param labelSetVersion - The label set version.
* @returns A valid EnsRainbowClientLabelSet object.
* @throws If `labelSetVersion` is defined without `labelSetId`.
*/
declare function buildEnsRainbowClientLabelSet(labelSetId?: LabelSetId, labelSetVersion?: LabelSetVersion): EnsRainbowClientLabelSet;
/**
* Validates that the server's label set is compatible with the client's requested label set.
* @param serverSet - The label set provided by the server.
* @param clientSet - The label set requested by the client.
* @throws If the server set is not compatible with the client set.
*/
declare function validateSupportedLabelSetAndVersion(serverSet: EnsRainbowServerLabelSet, clientSet: EnsRainbowClientLabelSet): void;
/**
* Parses a string into a non-negative integer.
* @param input The string to parse
* @returns The parsed non-negative integer
* @throws Error if the input is not a valid non-negative integer
*/
declare function parseNonNegativeInteger(maybeNumber: string): number;
/**
* Serializes a {@link ChainConfig} object.
*/
declare function serializeIndexedChainIds(indexedChainIds: Set<ChainId>): SerializedIndexedChainIds;
/**
* Serialize a {@link ENSIndexerPublicConfig} object.
*/
declare function serializeENSIndexerPublicConfig(config: ENSIndexerPublicConfig): SerializedENSIndexerPublicConfig;
/**
* The type of indexing configuration for a chain.
*/
declare const ChainIndexingConfigTypeIds: {
/**
* Represents that indexing of the chain should be performed for an indefinite range.
*/
readonly Indefinite: "indefinite";
/**
* Represents that indexing of the chain should be performed for a definite range.
*/
readonly Definite: "definite";
};
/**
* The derived string union of possible {@link ChainIndexingConfigTypeIds}.
*/
type ChainIndexingConfigTypeId = (typeof ChainIndexingConfigTypeIds)[keyof typeof ChainIndexingConfigTypeIds];
/**
* Chain indexing config for a chain whose indexing config `configType` is
* {@link ChainIndexingConfigTypeIds.Indefinite}.
*
* Invariants:
* - `configType` is always `ChainIndexingConfigTypeIds.Indefinite`.
*/
interface ChainIndexingConfigIndefinite {
/**
* The type of chain indexing config.
*/
configType: typeof ChainIndexingConfigTypeIds.Indefinite;
/**
* A {@link BlockRef} to the block where indexing of the chain should start.
*/
startBlock: BlockRef;
}
/**
* Chain indexing config for a chain whose indexing config `configType` is
* {@link ChainIndexingConfigTypeIds.Definite}.
*
* Invariants:
* - `configType` is always `ChainIndexingConfigTypeIds.Definite`.
* - `startBlock` is always before or the same as `endBlock`.
*/
interface ChainIndexingConfigDefinite {
/**
* The type of chain indexing config.
*/
configType: typeof ChainIndexingConfigTypeIds.Definite;
/**
* A {@link BlockRef} to the block where indexing of the chain should start.
*/
startBlock: BlockRef;
/**
* A {@link BlockRef} to the block where indexing of the chain should end.
*/
endBlock: BlockRef;
}
/**
* Indexing configuration for a chain.
*
* Use