@ensnode/ensrainbow-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 NameHash
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # ENSRainbow SDK
2
+
3
+ TypeScript library for interacting with the [ENSRainbow API](https://github.com/namehash/ensnode/tree/main/apps/ensrainbow).
4
+
5
+ Learn more about [ENSRainbow](https://ensrainbow.io) and [ENSNode](https://ensnode.io).
6
+
7
+ ## API Reference
8
+
9
+ ### Heal Label
10
+ Attempt to heal a labelhash to its original label.
11
+
12
+ ```typescript
13
+ const response = await client.heal(
14
+ "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc"
15
+ );
16
+
17
+ console.log(response);
18
+
19
+ // Output:
20
+ // {
21
+ // status: "success",
22
+ // label: "vitalik"
23
+ // }
24
+ ```
25
+
26
+ ### Label Count
27
+ Get Count of Healable Labels
28
+
29
+ ```typescript
30
+ const response = await client.count();
31
+
32
+ console.log(response);
33
+
34
+ // {
35
+ // "status": "success",
36
+ // "count": 133856894,
37
+ // "timestamp": "2024-01-30T11:18:56Z"
38
+ // }
39
+ ```
40
+
41
+ ### Health Check
42
+ Simple verification that the service is running, either in your local setup or for the provided hosted instance
43
+
44
+ ```typescript
45
+ const response = await client.health();
46
+
47
+ console.log(response);
48
+
49
+ // {
50
+ // "status": "ok",
51
+ // }
52
+ ```
53
+
54
+ ### Response Types & Error Handling
55
+ Each API endpoint has a designated response type that includes a successful and an erroneous response to account for possible mishaps that could occur during a request.
56
+
57
+ Below is an example of a failed `heal` operation, that shows the resulting error returned by the SDK
58
+
59
+ ```typescript
60
+ const notFoundResponse = await client.heal(
61
+ "0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264"
62
+ );
63
+
64
+ console.log(notFoundResponse);
65
+
66
+ // Output:
67
+ // {
68
+ // status: "error",
69
+ // error: "Label not found",
70
+ // errorCode: 404
71
+ // }
72
+ ```
73
+
74
+ ## Contact Us
75
+
76
+ Visit our [website](https://namehashlabs.org/) to get in contact.
77
+
78
+ ## License
79
+
80
+ Licensed under the MIT License, Copyright © 2025-present [NameHash Labs](https://namehashlabs.org).
81
+
82
+ See [LICENSE](./LICENSE) for more information.
@@ -0,0 +1,216 @@
1
+ import { Labelhash } from '@ensnode/utils/types';
2
+
3
+ declare namespace EnsRainbow {
4
+ export type ApiClientOptions = EnsRainbowApiClientOptions;
5
+ export interface ApiClient {
6
+ count(): Promise<CountResponse>;
7
+ heal(labelhash: Labelhash): Promise<HealResponse>;
8
+ health(): Promise<HealthResponse>;
9
+ getOptions(): Readonly<EnsRainbowApiClientOptions>;
10
+ }
11
+ type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];
12
+ type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
13
+ export interface HealthResponse {
14
+ status: "ok";
15
+ }
16
+ export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {
17
+ status: Status;
18
+ label?: string | never;
19
+ error?: string | never;
20
+ errorCode?: Error | never;
21
+ }
22
+ export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {
23
+ status: typeof StatusCode.Success;
24
+ label: string;
25
+ error?: never;
26
+ errorCode?: never;
27
+ }
28
+ export interface HealNotFoundError extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {
29
+ status: typeof StatusCode.Error;
30
+ label?: never;
31
+ error: string;
32
+ errorCode: typeof ErrorCode.NotFound;
33
+ }
34
+ export interface HealServerError extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {
35
+ status: typeof StatusCode.Error;
36
+ label?: never;
37
+ error: string;
38
+ errorCode: typeof ErrorCode.ServerError;
39
+ }
40
+ export interface HealBadRequestError extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {
41
+ status: typeof StatusCode.Error;
42
+ label?: never;
43
+ error: string;
44
+ errorCode: typeof ErrorCode.BadRequest;
45
+ }
46
+ export type HealResponse = HealSuccess | HealNotFoundError | HealServerError | HealBadRequestError;
47
+ export type HealError = Exclude<HealResponse, HealSuccess>;
48
+ /**
49
+ * Server errors should not be cached.
50
+ */
51
+ export type CacheableHealResponse = Exclude<HealResponse, HealServerError>;
52
+ export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {
53
+ status: Status;
54
+ count?: number | never;
55
+ timestamp?: string | never;
56
+ error?: string | never;
57
+ errorCode?: Error | never;
58
+ }
59
+ export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {
60
+ status: typeof StatusCode.Success;
61
+ /** The total count of labels that can be healed by the ENSRainbow instance. Always a non-negative integer. */
62
+ count: number;
63
+ timestamp: string;
64
+ error?: never;
65
+ errorCode?: never;
66
+ }
67
+ export interface CountServerError extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {
68
+ status: typeof StatusCode.Error;
69
+ count?: never;
70
+ timestamp?: never;
71
+ error: string;
72
+ errorCode: typeof ErrorCode.ServerError;
73
+ }
74
+ export type CountResponse = CountSuccess | CountServerError;
75
+ export { };
76
+ }
77
+ interface EnsRainbowApiClientOptions {
78
+ /**
79
+ * The maximum number of `HealResponse` values to cache.
80
+ * Must be a non-negative integer.
81
+ * Setting to 0 will disable caching.
82
+ */
83
+ cacheCapacity: number;
84
+ /**
85
+ * The URL of an ENSRainbow API endpoint.
86
+ */
87
+ endpointUrl: URL;
88
+ }
89
+ /**
90
+ * ENSRainbow API client
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * // default options
95
+ * const client = new EnsRainbowApiClient();
96
+ * // custom options
97
+ * const client = new EnsRainbowApiClient({
98
+ * endpointUrl: new URL("https://api.ensrainbow.io"),
99
+ * });
100
+ * ```
101
+ */
102
+ declare class EnsRainbowApiClient implements EnsRainbow.ApiClient {
103
+ private readonly options;
104
+ private readonly cache;
105
+ static readonly DEFAULT_CACHE_CAPACITY = 1000;
106
+ /**
107
+ * Create default client options.
108
+ *
109
+ * @returns default options
110
+ */
111
+ static defaultOptions(): EnsRainbow.ApiClientOptions;
112
+ constructor(options?: Partial<EnsRainbow.ApiClientOptions>);
113
+ /**
114
+ * Attempt to heal a labelhash to its original label.
115
+ *
116
+ * Note on returned labels: ENSRainbow returns labels exactly as they are
117
+ * represented in source rainbow table data. This means:
118
+ *
119
+ * - Labels may or may not be ENS-normalized
120
+ * - Labels can contain any valid string, including dots, null bytes, or be empty
121
+ * - Clients should handle all possible string values appropriately
122
+ *
123
+ * @param labelhash all lowercase 64-digit hex string with 0x prefix (total length of 66 characters)
124
+ * @returns a `HealResponse` indicating the result of the request and the healed label if successful
125
+ * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const response = await client.heal(
130
+ * "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc"
131
+ * );
132
+ *
133
+ * console.log(response);
134
+ *
135
+ * // Output:
136
+ * // {
137
+ * // status: "success",
138
+ * // label: "vitalik"
139
+ * // }
140
+ *
141
+ * const notFoundResponse = await client.heal(
142
+ * "0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264"
143
+ * );
144
+ *
145
+ * console.log(notFoundResponse);
146
+ *
147
+ * // Output:
148
+ * // {
149
+ * // status: "error",
150
+ * // error: "Label not found",
151
+ * // errorCode: 404
152
+ * // }
153
+ * ```
154
+ */
155
+ heal(labelhash: Labelhash): Promise<EnsRainbow.HealResponse>;
156
+ /**
157
+ * Get Count of Healable Labels
158
+ *
159
+ * @returns a `CountResponse` indicating the result and the timestamp of the request and the number of healable labels if successful
160
+ * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs
161
+ *
162
+ * @example
163
+ *
164
+ * const response = await client.count();
165
+ *
166
+ * console.log(response);
167
+ *
168
+ * // {
169
+ * // "status": "success",
170
+ * // "count": 133856894,
171
+ * // "timestamp": "2024-01-30T11:18:56Z"
172
+ * // }
173
+ *
174
+ */
175
+ count(): Promise<EnsRainbow.CountResponse>;
176
+ /**
177
+ *
178
+ * Simple verification that the service is running, either in your local setup or for the provided hosted instance
179
+ *
180
+ * @returns a status of ENS Rainbow service
181
+ * @example
182
+ *
183
+ * const response = await client.health();
184
+ *
185
+ * console.log(response);
186
+ *
187
+ * // {
188
+ * // "status": "ok",
189
+ * // }
190
+ */
191
+ health(): Promise<EnsRainbow.HealthResponse>;
192
+ /**
193
+ * Get a copy of the current client options.
194
+ *
195
+ * @returns a copy of the current client options.
196
+ */
197
+ getOptions(): Readonly<EnsRainbowApiClientOptions>;
198
+ }
199
+ /**
200
+ * Determine if a heal response is an error.
201
+ *
202
+ * @param response the heal response to check
203
+ * @returns true if the response is an error, false otherwise
204
+ */
205
+ declare const isHealError: (response: EnsRainbow.HealResponse) => response is EnsRainbow.HealError;
206
+ /**
207
+ * Determine if a heal response is cacheable.
208
+ *
209
+ * Server errors at not cachable and should be retried.
210
+ *
211
+ * @param response the heal response to check
212
+ * @returns true if the response is cacheable, false otherwise
213
+ */
214
+ declare const isCacheableHealResponse: (response: EnsRainbow.HealResponse) => response is EnsRainbow.CacheableHealResponse;
215
+
216
+ export { EnsRainbow, EnsRainbowApiClient, type EnsRainbowApiClientOptions, isCacheableHealResponse, isHealError };
package/dist/client.js ADDED
@@ -0,0 +1,161 @@
1
+ // src/client.ts
2
+ import { LruCache } from "@ensnode/utils/cache";
3
+
4
+ // src/consts.ts
5
+ var DEFAULT_ENSRAINBOW_URL = "https://api.ensrainbow.io";
6
+ var StatusCode = {
7
+ Success: "success",
8
+ Error: "error"
9
+ };
10
+ var ErrorCode = {
11
+ BadRequest: 400,
12
+ NotFound: 404,
13
+ ServerError: 500
14
+ };
15
+
16
+ // src/client.ts
17
+ var EnsRainbowApiClient = class _EnsRainbowApiClient {
18
+ options;
19
+ cache;
20
+ static DEFAULT_CACHE_CAPACITY = 1e3;
21
+ /**
22
+ * Create default client options.
23
+ *
24
+ * @returns default options
25
+ */
26
+ static defaultOptions() {
27
+ return {
28
+ endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),
29
+ cacheCapacity: _EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY
30
+ };
31
+ }
32
+ constructor(options = {}) {
33
+ this.options = {
34
+ ..._EnsRainbowApiClient.defaultOptions(),
35
+ ...options
36
+ };
37
+ this.cache = new LruCache(
38
+ this.options.cacheCapacity
39
+ );
40
+ }
41
+ /**
42
+ * Attempt to heal a labelhash to its original label.
43
+ *
44
+ * Note on returned labels: ENSRainbow returns labels exactly as they are
45
+ * represented in source rainbow table data. This means:
46
+ *
47
+ * - Labels may or may not be ENS-normalized
48
+ * - Labels can contain any valid string, including dots, null bytes, or be empty
49
+ * - Clients should handle all possible string values appropriately
50
+ *
51
+ * @param labelhash all lowercase 64-digit hex string with 0x prefix (total length of 66 characters)
52
+ * @returns a `HealResponse` indicating the result of the request and the healed label if successful
53
+ * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const response = await client.heal(
58
+ * "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc"
59
+ * );
60
+ *
61
+ * console.log(response);
62
+ *
63
+ * // Output:
64
+ * // {
65
+ * // status: "success",
66
+ * // label: "vitalik"
67
+ * // }
68
+ *
69
+ * const notFoundResponse = await client.heal(
70
+ * "0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264"
71
+ * );
72
+ *
73
+ * console.log(notFoundResponse);
74
+ *
75
+ * // Output:
76
+ * // {
77
+ * // status: "error",
78
+ * // error: "Label not found",
79
+ * // errorCode: 404
80
+ * // }
81
+ * ```
82
+ */
83
+ async heal(labelhash) {
84
+ const cachedResult = this.cache.get(labelhash);
85
+ if (cachedResult) {
86
+ return cachedResult;
87
+ }
88
+ const response = await fetch(new URL(`/v1/heal/${labelhash}`, this.options.endpointUrl));
89
+ const healResponse = await response.json();
90
+ if (isCacheableHealResponse(healResponse)) {
91
+ this.cache.set(labelhash, healResponse);
92
+ }
93
+ return healResponse;
94
+ }
95
+ /**
96
+ * Get Count of Healable Labels
97
+ *
98
+ * @returns a `CountResponse` indicating the result and the timestamp of the request and the number of healable labels if successful
99
+ * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs
100
+ *
101
+ * @example
102
+ *
103
+ * const response = await client.count();
104
+ *
105
+ * console.log(response);
106
+ *
107
+ * // {
108
+ * // "status": "success",
109
+ * // "count": 133856894,
110
+ * // "timestamp": "2024-01-30T11:18:56Z"
111
+ * // }
112
+ *
113
+ */
114
+ async count() {
115
+ const response = await fetch(new URL("/v1/labels/count", this.options.endpointUrl));
116
+ return response.json();
117
+ }
118
+ /**
119
+ *
120
+ * Simple verification that the service is running, either in your local setup or for the provided hosted instance
121
+ *
122
+ * @returns a status of ENS Rainbow service
123
+ * @example
124
+ *
125
+ * const response = await client.health();
126
+ *
127
+ * console.log(response);
128
+ *
129
+ * // {
130
+ * // "status": "ok",
131
+ * // }
132
+ */
133
+ async health() {
134
+ const response = await fetch(new URL("/health", this.options.endpointUrl));
135
+ return response.json();
136
+ }
137
+ /**
138
+ * Get a copy of the current client options.
139
+ *
140
+ * @returns a copy of the current client options.
141
+ */
142
+ getOptions() {
143
+ const deepCopy = {
144
+ cacheCapacity: this.options.cacheCapacity,
145
+ endpointUrl: new URL(this.options.endpointUrl.href)
146
+ };
147
+ return Object.freeze(deepCopy);
148
+ }
149
+ };
150
+ var isHealError = (response) => {
151
+ return response.status === StatusCode.Error;
152
+ };
153
+ var isCacheableHealResponse = (response) => {
154
+ return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;
155
+ };
156
+ export {
157
+ EnsRainbowApiClient,
158
+ isCacheableHealResponse,
159
+ isHealError
160
+ };
161
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/consts.ts"],"sourcesContent":["import type { Cache } from \"@ensnode/utils/cache\";\nimport { LruCache } from \"@ensnode/utils/cache\";\nimport type { Labelhash } from \"@ensnode/utils/types\";\nimport { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from \"./consts\";\n\nexport namespace EnsRainbow {\n export type ApiClientOptions = EnsRainbowApiClientOptions;\n\n export interface ApiClient {\n count(): Promise<CountResponse>;\n\n heal(labelhash: Labelhash): Promise<HealResponse>;\n\n health(): Promise<HealthResponse>;\n\n getOptions(): Readonly<EnsRainbowApiClientOptions>;\n }\n\n type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];\n\n type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n export interface HealthResponse {\n status: \"ok\";\n }\n\n export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n label?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n label: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface HealNotFoundError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.NotFound;\n }\n\n export interface HealServerError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface HealBadRequestError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.BadRequest;\n }\n\n export type HealResponse =\n | HealSuccess\n | HealNotFoundError\n | HealServerError\n | HealBadRequestError;\n export type HealError = Exclude<HealResponse, HealSuccess>;\n\n /**\n * Server errors should not be cached.\n */\n export type CacheableHealResponse = Exclude<HealResponse, HealServerError>;\n\n export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n count?: number | never;\n timestamp?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n /** The total count of labels that can be healed by the ENSRainbow instance. Always a non-negative integer. */\n count: number;\n timestamp: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface CountServerError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export type CountResponse = CountSuccess | CountServerError;\n}\n\nexport interface EnsRainbowApiClientOptions {\n /**\n * The maximum number of `HealResponse` values to cache.\n * Must be a non-negative integer.\n * Setting to 0 will disable caching.\n */\n cacheCapacity: number;\n\n /**\n * The URL of an ENSRainbow API endpoint.\n */\n endpointUrl: URL;\n}\n\n/**\n * ENSRainbow API client\n *\n * @example\n * ```typescript\n * // default options\n * const client = new EnsRainbowApiClient();\n * // custom options\n * const client = new EnsRainbowApiClient({\n * endpointUrl: new URL(\"https://api.ensrainbow.io\"),\n * });\n * ```\n */\nexport class EnsRainbowApiClient implements EnsRainbow.ApiClient {\n private readonly options: EnsRainbowApiClientOptions;\n private readonly cache: Cache<Labelhash, EnsRainbow.CacheableHealResponse>;\n\n public static readonly DEFAULT_CACHE_CAPACITY = 1000;\n\n /**\n * Create default client options.\n *\n * @returns default options\n */\n static defaultOptions(): EnsRainbow.ApiClientOptions {\n return {\n endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),\n cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,\n };\n }\n\n constructor(options: Partial<EnsRainbow.ApiClientOptions> = {}) {\n this.options = {\n ...EnsRainbowApiClient.defaultOptions(),\n ...options,\n };\n\n this.cache = new LruCache<Labelhash, EnsRainbow.CacheableHealResponse>(\n this.options.cacheCapacity,\n );\n }\n\n /**\n * Attempt to heal a labelhash to its original label.\n *\n * Note on returned labels: ENSRainbow returns labels exactly as they are\n * represented in source rainbow table data. This means:\n *\n * - Labels may or may not be ENS-normalized\n * - Labels can contain any valid string, including dots, null bytes, or be empty\n * - Clients should handle all possible string values appropriately\n *\n * @param labelhash all lowercase 64-digit hex string with 0x prefix (total length of 66 characters)\n * @returns a `HealResponse` indicating the result of the request and the healed label if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs\n *\n * @example\n * ```typescript\n * const response = await client.heal(\n * \"0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc\"\n * );\n *\n * console.log(response);\n *\n * // Output:\n * // {\n * // status: \"success\",\n * // label: \"vitalik\"\n * // }\n *\n * const notFoundResponse = await client.heal(\n * \"0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264\"\n * );\n *\n * console.log(notFoundResponse);\n *\n * // Output:\n * // {\n * // status: \"error\",\n * // error: \"Label not found\",\n * // errorCode: 404\n * // }\n * ```\n */\n async heal(labelhash: Labelhash): Promise<EnsRainbow.HealResponse> {\n const cachedResult = this.cache.get(labelhash);\n\n if (cachedResult) {\n return cachedResult;\n }\n\n const response = await fetch(new URL(`/v1/heal/${labelhash}`, this.options.endpointUrl));\n const healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n if (isCacheableHealResponse(healResponse)) {\n this.cache.set(labelhash, healResponse);\n }\n\n return healResponse;\n }\n\n /**\n * Get Count of Healable Labels\n *\n * @returns a `CountResponse` indicating the result and the timestamp of the request and the number of healable labels if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs\n *\n * @example\n *\n * const response = await client.count();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"success\",\n * // \"count\": 133856894,\n * // \"timestamp\": \"2024-01-30T11:18:56Z\"\n * // }\n *\n */\n async count(): Promise<EnsRainbow.CountResponse> {\n const response = await fetch(new URL(\"/v1/labels/count\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.CountResponse>;\n }\n\n /**\n *\n * Simple verification that the service is running, either in your local setup or for the provided hosted instance\n *\n * @returns a status of ENS Rainbow service\n * @example\n *\n * const response = await client.health();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"ok\",\n * // }\n */\n async health(): Promise<EnsRainbow.HealthResponse> {\n const response = await fetch(new URL(\"/health\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.HealthResponse>;\n }\n\n /**\n * Get a copy of the current client options.\n *\n * @returns a copy of the current client options.\n */\n getOptions(): Readonly<EnsRainbowApiClientOptions> {\n // build a deep copy to prevent modification\n const deepCopy = {\n cacheCapacity: this.options.cacheCapacity,\n endpointUrl: new URL(this.options.endpointUrl.href),\n } satisfies EnsRainbowApiClientOptions;\n\n return Object.freeze(deepCopy);\n }\n}\n\n/**\n * Determine if a heal response is an error.\n *\n * @param response the heal response to check\n * @returns true if the response is an error, false otherwise\n */\nexport const isHealError = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.HealError => {\n return response.status === StatusCode.Error;\n};\n\n/**\n * Determine if a heal response is cacheable.\n *\n * Server errors at not cachable and should be retried.\n *\n * @param response the heal response to check\n * @returns true if the response is cacheable, false otherwise\n */\nexport const isCacheableHealResponse = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.CacheableHealResponse => {\n return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;\n};\n","export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n} as const;\n"],"mappings":";AACA,SAAS,gBAAgB;;;ACDlB,IAAM,yBAAyB;AAE/B,IAAM,aAAa;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AACT;AAEO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,aAAa;AACf;;;ADyHO,IAAM,sBAAN,MAAM,qBAAoD;AAAA,EAC9C;AAAA,EACA;AAAA,EAEjB,OAAuB,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhD,OAAO,iBAA8C;AACnD,WAAO;AAAA,MACL,aAAa,IAAI,IAAI,sBAAsB;AAAA,MAC3C,eAAe,qBAAoB;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,YAAY,UAAgD,CAAC,GAAG;AAC9D,SAAK,UAAU;AAAA,MACb,GAAG,qBAAoB,eAAe;AAAA,MACtC,GAAG;AAAA,IACL;AAEA,SAAK,QAAQ,IAAI;AAAA,MACf,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4CA,MAAM,KAAK,WAAwD;AACjE,UAAM,eAAe,KAAK,MAAM,IAAI,SAAS;AAE7C,QAAI,cAAc;AAChB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,YAAY,SAAS,IAAI,KAAK,QAAQ,WAAW,CAAC;AACvF,UAAM,eAAgB,MAAM,SAAS,KAAK;AAE1C,QAAI,wBAAwB,YAAY,GAAG;AACzC,WAAK,MAAM,IAAI,WAAW,YAAY;AAAA,IACxC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,QAA2C;AAC/C,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,oBAAoB,KAAK,QAAQ,WAAW,CAAC;AAElF,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,SAA6C;AACjD,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,WAAW,KAAK,QAAQ,WAAW,CAAC;AAEzE,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAmD;AAEjD,UAAM,WAAW;AAAA,MACf,eAAe,KAAK,QAAQ;AAAA,MAC5B,aAAa,IAAI,IAAI,KAAK,QAAQ,YAAY,IAAI;AAAA,IACpD;AAEA,WAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B;AACF;AAQO,IAAM,cAAc,CACzB,aACqC;AACrC,SAAO,SAAS,WAAW,WAAW;AACxC;AAUO,IAAM,0BAA0B,CACrC,aACiD;AACjD,SAAO,SAAS,WAAW,WAAW,WAAW,SAAS,cAAc,UAAU;AACpF;","names":[]}
@@ -0,0 +1,12 @@
1
+ declare const DEFAULT_ENSRAINBOW_URL: "https://api.ensrainbow.io";
2
+ declare const StatusCode: {
3
+ readonly Success: "success";
4
+ readonly Error: "error";
5
+ };
6
+ declare const ErrorCode: {
7
+ readonly BadRequest: 400;
8
+ readonly NotFound: 404;
9
+ readonly ServerError: 500;
10
+ };
11
+
12
+ export { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode };
package/dist/consts.js ADDED
@@ -0,0 +1,17 @@
1
+ // src/consts.ts
2
+ var DEFAULT_ENSRAINBOW_URL = "https://api.ensrainbow.io";
3
+ var StatusCode = {
4
+ Success: "success",
5
+ Error: "error"
6
+ };
7
+ var ErrorCode = {
8
+ BadRequest: 400,
9
+ NotFound: 404,
10
+ ServerError: 500
11
+ };
12
+ export {
13
+ DEFAULT_ENSRAINBOW_URL,
14
+ ErrorCode,
15
+ StatusCode
16
+ };
17
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/consts.ts"],"sourcesContent":["export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n} as const;\n"],"mappings":";AAAO,IAAM,yBAAyB;AAE/B,IAAM,aAAa;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AACT;AAEO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,aAAa;AACf;","names":[]}
@@ -0,0 +1,5 @@
1
+ export { EnsRainbow, EnsRainbowApiClient, EnsRainbowApiClientOptions, isCacheableHealResponse, isHealError } from './client.js';
2
+ export { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from './consts.js';
3
+ export { labelHashToBytes } from './labelUtils.js';
4
+ import '@ensnode/utils/types';
5
+ import 'viem';
package/dist/index.js ADDED
@@ -0,0 +1,191 @@
1
+ // src/client.ts
2
+ import { LruCache } from "@ensnode/utils/cache";
3
+
4
+ // src/consts.ts
5
+ var DEFAULT_ENSRAINBOW_URL = "https://api.ensrainbow.io";
6
+ var StatusCode = {
7
+ Success: "success",
8
+ Error: "error"
9
+ };
10
+ var ErrorCode = {
11
+ BadRequest: 400,
12
+ NotFound: 404,
13
+ ServerError: 500
14
+ };
15
+
16
+ // src/client.ts
17
+ var EnsRainbowApiClient = class _EnsRainbowApiClient {
18
+ options;
19
+ cache;
20
+ static DEFAULT_CACHE_CAPACITY = 1e3;
21
+ /**
22
+ * Create default client options.
23
+ *
24
+ * @returns default options
25
+ */
26
+ static defaultOptions() {
27
+ return {
28
+ endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),
29
+ cacheCapacity: _EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY
30
+ };
31
+ }
32
+ constructor(options = {}) {
33
+ this.options = {
34
+ ..._EnsRainbowApiClient.defaultOptions(),
35
+ ...options
36
+ };
37
+ this.cache = new LruCache(
38
+ this.options.cacheCapacity
39
+ );
40
+ }
41
+ /**
42
+ * Attempt to heal a labelhash to its original label.
43
+ *
44
+ * Note on returned labels: ENSRainbow returns labels exactly as they are
45
+ * represented in source rainbow table data. This means:
46
+ *
47
+ * - Labels may or may not be ENS-normalized
48
+ * - Labels can contain any valid string, including dots, null bytes, or be empty
49
+ * - Clients should handle all possible string values appropriately
50
+ *
51
+ * @param labelhash all lowercase 64-digit hex string with 0x prefix (total length of 66 characters)
52
+ * @returns a `HealResponse` indicating the result of the request and the healed label if successful
53
+ * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const response = await client.heal(
58
+ * "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc"
59
+ * );
60
+ *
61
+ * console.log(response);
62
+ *
63
+ * // Output:
64
+ * // {
65
+ * // status: "success",
66
+ * // label: "vitalik"
67
+ * // }
68
+ *
69
+ * const notFoundResponse = await client.heal(
70
+ * "0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264"
71
+ * );
72
+ *
73
+ * console.log(notFoundResponse);
74
+ *
75
+ * // Output:
76
+ * // {
77
+ * // status: "error",
78
+ * // error: "Label not found",
79
+ * // errorCode: 404
80
+ * // }
81
+ * ```
82
+ */
83
+ async heal(labelhash) {
84
+ const cachedResult = this.cache.get(labelhash);
85
+ if (cachedResult) {
86
+ return cachedResult;
87
+ }
88
+ const response = await fetch(new URL(`/v1/heal/${labelhash}`, this.options.endpointUrl));
89
+ const healResponse = await response.json();
90
+ if (isCacheableHealResponse(healResponse)) {
91
+ this.cache.set(labelhash, healResponse);
92
+ }
93
+ return healResponse;
94
+ }
95
+ /**
96
+ * Get Count of Healable Labels
97
+ *
98
+ * @returns a `CountResponse` indicating the result and the timestamp of the request and the number of healable labels if successful
99
+ * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs
100
+ *
101
+ * @example
102
+ *
103
+ * const response = await client.count();
104
+ *
105
+ * console.log(response);
106
+ *
107
+ * // {
108
+ * // "status": "success",
109
+ * // "count": 133856894,
110
+ * // "timestamp": "2024-01-30T11:18:56Z"
111
+ * // }
112
+ *
113
+ */
114
+ async count() {
115
+ const response = await fetch(new URL("/v1/labels/count", this.options.endpointUrl));
116
+ return response.json();
117
+ }
118
+ /**
119
+ *
120
+ * Simple verification that the service is running, either in your local setup or for the provided hosted instance
121
+ *
122
+ * @returns a status of ENS Rainbow service
123
+ * @example
124
+ *
125
+ * const response = await client.health();
126
+ *
127
+ * console.log(response);
128
+ *
129
+ * // {
130
+ * // "status": "ok",
131
+ * // }
132
+ */
133
+ async health() {
134
+ const response = await fetch(new URL("/health", this.options.endpointUrl));
135
+ return response.json();
136
+ }
137
+ /**
138
+ * Get a copy of the current client options.
139
+ *
140
+ * @returns a copy of the current client options.
141
+ */
142
+ getOptions() {
143
+ const deepCopy = {
144
+ cacheCapacity: this.options.cacheCapacity,
145
+ endpointUrl: new URL(this.options.endpointUrl.href)
146
+ };
147
+ return Object.freeze(deepCopy);
148
+ }
149
+ };
150
+ var isHealError = (response) => {
151
+ return response.status === StatusCode.Error;
152
+ };
153
+ var isCacheableHealResponse = (response) => {
154
+ return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;
155
+ };
156
+
157
+ // src/label-utils.ts
158
+ import { hexToBytes } from "viem";
159
+ function labelHashToBytes(labelHash) {
160
+ try {
161
+ if (labelHash.length !== 66) {
162
+ throw new Error(`Invalid labelhash length ${labelHash.length} characters (expected 66)`);
163
+ }
164
+ if (labelHash !== labelHash.toLowerCase()) {
165
+ throw new Error("Labelhash must be in lowercase");
166
+ }
167
+ if (!labelHash.startsWith("0x")) {
168
+ throw new Error("Labelhash must be 0x-prefixed");
169
+ }
170
+ const bytes = hexToBytes(labelHash);
171
+ if (bytes.length !== 32) {
172
+ throw new Error(`Invalid labelhash length ${bytes.length} bytes (expected 32)`);
173
+ }
174
+ return bytes;
175
+ } catch (e) {
176
+ if (e instanceof Error) {
177
+ throw e;
178
+ }
179
+ throw new Error("Invalid hex format");
180
+ }
181
+ }
182
+ export {
183
+ DEFAULT_ENSRAINBOW_URL,
184
+ EnsRainbowApiClient,
185
+ ErrorCode,
186
+ StatusCode,
187
+ isCacheableHealResponse,
188
+ isHealError,
189
+ labelHashToBytes
190
+ };
191
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/consts.ts","../src/label-utils.ts"],"sourcesContent":["import type { Cache } from \"@ensnode/utils/cache\";\nimport { LruCache } from \"@ensnode/utils/cache\";\nimport type { Labelhash } from \"@ensnode/utils/types\";\nimport { DEFAULT_ENSRAINBOW_URL, ErrorCode, StatusCode } from \"./consts\";\n\nexport namespace EnsRainbow {\n export type ApiClientOptions = EnsRainbowApiClientOptions;\n\n export interface ApiClient {\n count(): Promise<CountResponse>;\n\n heal(labelhash: Labelhash): Promise<HealResponse>;\n\n health(): Promise<HealthResponse>;\n\n getOptions(): Readonly<EnsRainbowApiClientOptions>;\n }\n\n type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];\n\n type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\n export interface HealthResponse {\n status: \"ok\";\n }\n\n export interface BaseHealResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n label?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface HealSuccess extends BaseHealResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n label: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface HealNotFoundError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.NotFound> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.NotFound;\n }\n\n export interface HealServerError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export interface HealBadRequestError\n extends BaseHealResponse<typeof StatusCode.Error, typeof ErrorCode.BadRequest> {\n status: typeof StatusCode.Error;\n label?: never;\n error: string;\n errorCode: typeof ErrorCode.BadRequest;\n }\n\n export type HealResponse =\n | HealSuccess\n | HealNotFoundError\n | HealServerError\n | HealBadRequestError;\n export type HealError = Exclude<HealResponse, HealSuccess>;\n\n /**\n * Server errors should not be cached.\n */\n export type CacheableHealResponse = Exclude<HealResponse, HealServerError>;\n\n export interface BaseCountResponse<Status extends StatusCode, Error extends ErrorCode> {\n status: Status;\n count?: number | never;\n timestamp?: string | never;\n error?: string | never;\n errorCode?: Error | never;\n }\n\n export interface CountSuccess extends BaseCountResponse<typeof StatusCode.Success, never> {\n status: typeof StatusCode.Success;\n /** The total count of labels that can be healed by the ENSRainbow instance. Always a non-negative integer. */\n count: number;\n timestamp: string;\n error?: never;\n errorCode?: never;\n }\n\n export interface CountServerError\n extends BaseCountResponse<typeof StatusCode.Error, typeof ErrorCode.ServerError> {\n status: typeof StatusCode.Error;\n count?: never;\n timestamp?: never;\n error: string;\n errorCode: typeof ErrorCode.ServerError;\n }\n\n export type CountResponse = CountSuccess | CountServerError;\n}\n\nexport interface EnsRainbowApiClientOptions {\n /**\n * The maximum number of `HealResponse` values to cache.\n * Must be a non-negative integer.\n * Setting to 0 will disable caching.\n */\n cacheCapacity: number;\n\n /**\n * The URL of an ENSRainbow API endpoint.\n */\n endpointUrl: URL;\n}\n\n/**\n * ENSRainbow API client\n *\n * @example\n * ```typescript\n * // default options\n * const client = new EnsRainbowApiClient();\n * // custom options\n * const client = new EnsRainbowApiClient({\n * endpointUrl: new URL(\"https://api.ensrainbow.io\"),\n * });\n * ```\n */\nexport class EnsRainbowApiClient implements EnsRainbow.ApiClient {\n private readonly options: EnsRainbowApiClientOptions;\n private readonly cache: Cache<Labelhash, EnsRainbow.CacheableHealResponse>;\n\n public static readonly DEFAULT_CACHE_CAPACITY = 1000;\n\n /**\n * Create default client options.\n *\n * @returns default options\n */\n static defaultOptions(): EnsRainbow.ApiClientOptions {\n return {\n endpointUrl: new URL(DEFAULT_ENSRAINBOW_URL),\n cacheCapacity: EnsRainbowApiClient.DEFAULT_CACHE_CAPACITY,\n };\n }\n\n constructor(options: Partial<EnsRainbow.ApiClientOptions> = {}) {\n this.options = {\n ...EnsRainbowApiClient.defaultOptions(),\n ...options,\n };\n\n this.cache = new LruCache<Labelhash, EnsRainbow.CacheableHealResponse>(\n this.options.cacheCapacity,\n );\n }\n\n /**\n * Attempt to heal a labelhash to its original label.\n *\n * Note on returned labels: ENSRainbow returns labels exactly as they are\n * represented in source rainbow table data. This means:\n *\n * - Labels may or may not be ENS-normalized\n * - Labels can contain any valid string, including dots, null bytes, or be empty\n * - Clients should handle all possible string values appropriately\n *\n * @param labelhash all lowercase 64-digit hex string with 0x prefix (total length of 66 characters)\n * @returns a `HealResponse` indicating the result of the request and the healed label if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs\n *\n * @example\n * ```typescript\n * const response = await client.heal(\n * \"0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc\"\n * );\n *\n * console.log(response);\n *\n * // Output:\n * // {\n * // status: \"success\",\n * // label: \"vitalik\"\n * // }\n *\n * const notFoundResponse = await client.heal(\n * \"0xf64dc17ae2e2b9b16dbcb8cb05f35a2e6080a5ff1dc53ac0bc48f0e79111f264\"\n * );\n *\n * console.log(notFoundResponse);\n *\n * // Output:\n * // {\n * // status: \"error\",\n * // error: \"Label not found\",\n * // errorCode: 404\n * // }\n * ```\n */\n async heal(labelhash: Labelhash): Promise<EnsRainbow.HealResponse> {\n const cachedResult = this.cache.get(labelhash);\n\n if (cachedResult) {\n return cachedResult;\n }\n\n const response = await fetch(new URL(`/v1/heal/${labelhash}`, this.options.endpointUrl));\n const healResponse = (await response.json()) as EnsRainbow.HealResponse;\n\n if (isCacheableHealResponse(healResponse)) {\n this.cache.set(labelhash, healResponse);\n }\n\n return healResponse;\n }\n\n /**\n * Get Count of Healable Labels\n *\n * @returns a `CountResponse` indicating the result and the timestamp of the request and the number of healable labels if successful\n * @throws if the request fails due to network failures, DNS lookup failures, request timeouts, CORS violations, or Invalid URLs\n *\n * @example\n *\n * const response = await client.count();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"success\",\n * // \"count\": 133856894,\n * // \"timestamp\": \"2024-01-30T11:18:56Z\"\n * // }\n *\n */\n async count(): Promise<EnsRainbow.CountResponse> {\n const response = await fetch(new URL(\"/v1/labels/count\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.CountResponse>;\n }\n\n /**\n *\n * Simple verification that the service is running, either in your local setup or for the provided hosted instance\n *\n * @returns a status of ENS Rainbow service\n * @example\n *\n * const response = await client.health();\n *\n * console.log(response);\n *\n * // {\n * // \"status\": \"ok\",\n * // }\n */\n async health(): Promise<EnsRainbow.HealthResponse> {\n const response = await fetch(new URL(\"/health\", this.options.endpointUrl));\n\n return response.json() as Promise<EnsRainbow.HealthResponse>;\n }\n\n /**\n * Get a copy of the current client options.\n *\n * @returns a copy of the current client options.\n */\n getOptions(): Readonly<EnsRainbowApiClientOptions> {\n // build a deep copy to prevent modification\n const deepCopy = {\n cacheCapacity: this.options.cacheCapacity,\n endpointUrl: new URL(this.options.endpointUrl.href),\n } satisfies EnsRainbowApiClientOptions;\n\n return Object.freeze(deepCopy);\n }\n}\n\n/**\n * Determine if a heal response is an error.\n *\n * @param response the heal response to check\n * @returns true if the response is an error, false otherwise\n */\nexport const isHealError = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.HealError => {\n return response.status === StatusCode.Error;\n};\n\n/**\n * Determine if a heal response is cacheable.\n *\n * Server errors at not cachable and should be retried.\n *\n * @param response the heal response to check\n * @returns true if the response is cacheable, false otherwise\n */\nexport const isCacheableHealResponse = (\n response: EnsRainbow.HealResponse,\n): response is EnsRainbow.CacheableHealResponse => {\n return response.status === StatusCode.Success || response.errorCode !== ErrorCode.ServerError;\n};\n","export const DEFAULT_ENSRAINBOW_URL = \"https://api.ensrainbow.io\" as const;\n\nexport const StatusCode = {\n Success: \"success\",\n Error: \"error\",\n} as const;\n\nexport const ErrorCode = {\n BadRequest: 400,\n NotFound: 404,\n ServerError: 500,\n} as const;\n","import type { Labelhash } from \"@ensnode/utils/types\";\nimport { ByteArray, hexToBytes } from \"viem\";\n\n/**\n * Converts a Labelhash to bytes, with validation\n * @param labelHash The Labelhash to convert\n * @returns A ByteArray containing the bytes\n * @throws Error if `labelHash` is not a valid 32-byte hex string\n */\nexport function labelHashToBytes(labelHash: Labelhash): ByteArray {\n try {\n if (labelHash.length !== 66) {\n throw new Error(`Invalid labelhash length ${labelHash.length} characters (expected 66)`);\n }\n if (labelHash !== labelHash.toLowerCase()) {\n throw new Error(\"Labelhash must be in lowercase\");\n }\n if (!labelHash.startsWith(\"0x\")) {\n throw new Error(\"Labelhash must be 0x-prefixed\");\n }\n const bytes = hexToBytes(labelHash);\n if (bytes.length !== 32) {\n // should be redundant but keeping it for the principle of defensive programming\n throw new Error(`Invalid labelhash length ${bytes.length} bytes (expected 32)`);\n }\n return bytes;\n } catch (e) {\n if (e instanceof Error) {\n throw e;\n }\n throw new Error(\"Invalid hex format\");\n }\n}\n"],"mappings":";AACA,SAAS,gBAAgB;;;ACDlB,IAAM,yBAAyB;AAE/B,IAAM,aAAa;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AACT;AAEO,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,aAAa;AACf;;;ADyHO,IAAM,sBAAN,MAAM,qBAAoD;AAAA,EAC9C;AAAA,EACA;AAAA,EAEjB,OAAuB,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhD,OAAO,iBAA8C;AACnD,WAAO;AAAA,MACL,aAAa,IAAI,IAAI,sBAAsB;AAAA,MAC3C,eAAe,qBAAoB;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,YAAY,UAAgD,CAAC,GAAG;AAC9D,SAAK,UAAU;AAAA,MACb,GAAG,qBAAoB,eAAe;AAAA,MACtC,GAAG;AAAA,IACL;AAEA,SAAK,QAAQ,IAAI;AAAA,MACf,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4CA,MAAM,KAAK,WAAwD;AACjE,UAAM,eAAe,KAAK,MAAM,IAAI,SAAS;AAE7C,QAAI,cAAc;AAChB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,YAAY,SAAS,IAAI,KAAK,QAAQ,WAAW,CAAC;AACvF,UAAM,eAAgB,MAAM,SAAS,KAAK;AAE1C,QAAI,wBAAwB,YAAY,GAAG;AACzC,WAAK,MAAM,IAAI,WAAW,YAAY;AAAA,IACxC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,QAA2C;AAC/C,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,oBAAoB,KAAK,QAAQ,WAAW,CAAC;AAElF,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,SAA6C;AACjD,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,WAAW,KAAK,QAAQ,WAAW,CAAC;AAEzE,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAmD;AAEjD,UAAM,WAAW;AAAA,MACf,eAAe,KAAK,QAAQ;AAAA,MAC5B,aAAa,IAAI,IAAI,KAAK,QAAQ,YAAY,IAAI;AAAA,IACpD;AAEA,WAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B;AACF;AAQO,IAAM,cAAc,CACzB,aACqC;AACrC,SAAO,SAAS,WAAW,WAAW;AACxC;AAUO,IAAM,0BAA0B,CACrC,aACiD;AACjD,SAAO,SAAS,WAAW,WAAW,WAAW,SAAS,cAAc,UAAU;AACpF;;;AEjTA,SAAoB,kBAAkB;AAQ/B,SAAS,iBAAiB,WAAiC;AAChE,MAAI;AACF,QAAI,UAAU,WAAW,IAAI;AAC3B,YAAM,IAAI,MAAM,4BAA4B,UAAU,MAAM,2BAA2B;AAAA,IACzF;AACA,QAAI,cAAc,UAAU,YAAY,GAAG;AACzC,YAAM,IAAI,MAAM,gCAAgC;AAAA,IAClD;AACA,QAAI,CAAC,UAAU,WAAW,IAAI,GAAG;AAC/B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,UAAM,QAAQ,WAAW,SAAS;AAClC,QAAI,MAAM,WAAW,IAAI;AAEvB,YAAM,IAAI,MAAM,4BAA4B,MAAM,MAAM,sBAAsB;AAAA,IAChF;AACA,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,aAAa,OAAO;AACtB,YAAM;AAAA,IACR;AACA,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AACF;","names":[]}
@@ -0,0 +1,3 @@
1
+ import '@ensnode/utils/types';
2
+ import 'viem';
3
+ export { labelHashToBytes } from './labelUtils.js';
@@ -0,0 +1,29 @@
1
+ // src/label-utils.ts
2
+ import { hexToBytes } from "viem";
3
+ function labelHashToBytes(labelHash) {
4
+ try {
5
+ if (labelHash.length !== 66) {
6
+ throw new Error(`Invalid labelhash length ${labelHash.length} characters (expected 66)`);
7
+ }
8
+ if (labelHash !== labelHash.toLowerCase()) {
9
+ throw new Error("Labelhash must be in lowercase");
10
+ }
11
+ if (!labelHash.startsWith("0x")) {
12
+ throw new Error("Labelhash must be 0x-prefixed");
13
+ }
14
+ const bytes = hexToBytes(labelHash);
15
+ if (bytes.length !== 32) {
16
+ throw new Error(`Invalid labelhash length ${bytes.length} bytes (expected 32)`);
17
+ }
18
+ return bytes;
19
+ } catch (e) {
20
+ if (e instanceof Error) {
21
+ throw e;
22
+ }
23
+ throw new Error("Invalid hex format");
24
+ }
25
+ }
26
+ export {
27
+ labelHashToBytes
28
+ };
29
+ //# sourceMappingURL=label-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/label-utils.ts"],"sourcesContent":["import type { Labelhash } from \"@ensnode/utils/types\";\nimport { ByteArray, hexToBytes } from \"viem\";\n\n/**\n * Converts a Labelhash to bytes, with validation\n * @param labelHash The Labelhash to convert\n * @returns A ByteArray containing the bytes\n * @throws Error if `labelHash` is not a valid 32-byte hex string\n */\nexport function labelHashToBytes(labelHash: Labelhash): ByteArray {\n try {\n if (labelHash.length !== 66) {\n throw new Error(`Invalid labelhash length ${labelHash.length} characters (expected 66)`);\n }\n if (labelHash !== labelHash.toLowerCase()) {\n throw new Error(\"Labelhash must be in lowercase\");\n }\n if (!labelHash.startsWith(\"0x\")) {\n throw new Error(\"Labelhash must be 0x-prefixed\");\n }\n const bytes = hexToBytes(labelHash);\n if (bytes.length !== 32) {\n // should be redundant but keeping it for the principle of defensive programming\n throw new Error(`Invalid labelhash length ${bytes.length} bytes (expected 32)`);\n }\n return bytes;\n } catch (e) {\n if (e instanceof Error) {\n throw e;\n }\n throw new Error(\"Invalid hex format\");\n }\n}\n"],"mappings":";AACA,SAAoB,kBAAkB;AAQ/B,SAAS,iBAAiB,WAAiC;AAChE,MAAI;AACF,QAAI,UAAU,WAAW,IAAI;AAC3B,YAAM,IAAI,MAAM,4BAA4B,UAAU,MAAM,2BAA2B;AAAA,IACzF;AACA,QAAI,cAAc,UAAU,YAAY,GAAG;AACzC,YAAM,IAAI,MAAM,gCAAgC;AAAA,IAClD;AACA,QAAI,CAAC,UAAU,WAAW,IAAI,GAAG;AAC/B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,UAAM,QAAQ,WAAW,SAAS;AAClC,QAAI,MAAM,WAAW,IAAI;AAEvB,YAAM,IAAI,MAAM,4BAA4B,MAAM,MAAM,sBAAsB;AAAA,IAChF;AACA,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,aAAa,OAAO;AACtB,YAAM;AAAA,IACR;AACA,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AACF;","names":[]}
@@ -0,0 +1,12 @@
1
+ import { Labelhash } from '@ensnode/utils/types';
2
+ import { ByteArray } from 'viem';
3
+
4
+ /**
5
+ * Converts a Labelhash to bytes, with validation
6
+ * @param labelHash The Labelhash to convert
7
+ * @returns A ByteArray containing the bytes
8
+ * @throws Error if `labelHash` is not a valid 32-byte hex string
9
+ */
10
+ declare function labelHashToBytes(labelHash: Labelhash): ByteArray;
11
+
12
+ export { labelHashToBytes };
@@ -0,0 +1,29 @@
1
+ // src/label-utils.ts
2
+ import { hexToBytes } from "viem";
3
+ function labelHashToBytes(labelHash) {
4
+ try {
5
+ if (labelHash.length !== 66) {
6
+ throw new Error(`Invalid labelhash length ${labelHash.length} characters (expected 66)`);
7
+ }
8
+ if (labelHash !== labelHash.toLowerCase()) {
9
+ throw new Error("Labelhash must be in lowercase");
10
+ }
11
+ if (!labelHash.startsWith("0x")) {
12
+ throw new Error("Labelhash must be 0x-prefixed");
13
+ }
14
+ const bytes = hexToBytes(labelHash);
15
+ if (bytes.length !== 32) {
16
+ throw new Error(`Invalid labelhash length ${bytes.length} bytes (expected 32)`);
17
+ }
18
+ return bytes;
19
+ } catch (e) {
20
+ if (e instanceof Error) {
21
+ throw e;
22
+ }
23
+ throw new Error("Invalid hex format");
24
+ }
25
+ }
26
+ export {
27
+ labelHashToBytes
28
+ };
29
+ //# sourceMappingURL=labelUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/label-utils.ts"],"sourcesContent":["import type { Labelhash } from \"@ensnode/utils/types\";\nimport { ByteArray, hexToBytes } from \"viem\";\n\n/**\n * Converts a Labelhash to bytes, with validation\n * @param labelHash The Labelhash to convert\n * @returns A ByteArray containing the bytes\n * @throws Error if `labelHash` is not a valid 32-byte hex string\n */\nexport function labelHashToBytes(labelHash: Labelhash): ByteArray {\n try {\n if (labelHash.length !== 66) {\n throw new Error(`Invalid labelhash length ${labelHash.length} characters (expected 66)`);\n }\n if (labelHash !== labelHash.toLowerCase()) {\n throw new Error(\"Labelhash must be in lowercase\");\n }\n if (!labelHash.startsWith(\"0x\")) {\n throw new Error(\"Labelhash must be 0x-prefixed\");\n }\n const bytes = hexToBytes(labelHash);\n if (bytes.length !== 32) {\n // should be redundant but keeping it for the principle of defensive programming\n throw new Error(`Invalid labelhash length ${bytes.length} bytes (expected 32)`);\n }\n return bytes;\n } catch (e) {\n if (e instanceof Error) {\n throw e;\n }\n throw new Error(\"Invalid hex format\");\n }\n}\n"],"mappings":";AACA,SAAoB,kBAAkB;AAQ/B,SAAS,iBAAiB,WAAiC;AAChE,MAAI;AACF,QAAI,UAAU,WAAW,IAAI;AAC3B,YAAM,IAAI,MAAM,4BAA4B,UAAU,MAAM,2BAA2B;AAAA,IACzF;AACA,QAAI,cAAc,UAAU,YAAY,GAAG;AACzC,YAAM,IAAI,MAAM,gCAAgC;AAAA,IAClD;AACA,QAAI,CAAC,UAAU,WAAW,IAAI,GAAG;AAC/B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,UAAM,QAAQ,WAAW,SAAS;AAClC,QAAI,MAAM,WAAW,IAAI;AAEvB,YAAM,IAAI,MAAM,4BAA4B,MAAM,MAAM,sBAAsB;AAAA,IAChF;AACA,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,aAAa,OAAO;AACtB,YAAM;AAAA,IACR;AACA,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@ensnode/ensrainbow-sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "ENSRainbow SDK for interacting with the ENSRainbow API.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/namehash/ensnode.git",
10
+ "directory": "packages/ensrainbow-sdk"
11
+ },
12
+ "homepage": "https://github.com/namehash/ensnode/tree/main/packages/ensrainbow-sdk",
13
+ "keywords": [
14
+ "ENS",
15
+ "ENSNode",
16
+ "ENSRainbow"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./client": {
24
+ "types": "./dist/client.d.ts",
25
+ "default": "./dist/client.js"
26
+ },
27
+ "./consts": {
28
+ "types": "./dist/consts.d.ts",
29
+ "default": "./dist/consts.js"
30
+ },
31
+ "./label-utils": {
32
+ "types": "./dist/label-utils.d.ts",
33
+ "default": "./dist/label-utils.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "peerDependencies": {
43
+ "viem": "^2.22.13"
44
+ },
45
+ "devDependencies": {
46
+ "@biomejs/biome": "^1.9.4",
47
+ "@ensnode/shared-configs": "",
48
+ "@ensnode/utils": "",
49
+ "tsup": "^8.3.6",
50
+ "typescript": "^5.7.3",
51
+ "vitest": "^3.0.5"
52
+ },
53
+ "scripts": {
54
+ "prepublish": "tsup",
55
+ "test": "vitest",
56
+ "typecheck": "tsc --noEmit",
57
+ "lint": "biome check --write",
58
+ "lint:ci": "biome ci"
59
+ },
60
+ "main": "./dist/index.js",
61
+ "module": "./dist/index.mjs",
62
+ "types": "./dist/index.d.ts"
63
+ }