@satianurag/hiero-mirror-client 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/dist/index.mjs ADDED
@@ -0,0 +1,2218 @@
1
+ import { parse } from "lossless-json";
2
+ //#region src/errors/HieroError.ts
3
+ /**
4
+ * Base error class for all Hiero Mirror Client errors.
5
+ *
6
+ * Modeled after Stripe's error hierarchy — every SDK error extends this class,
7
+ * enabling `instanceof HieroError` to catch all SDK-specific errors.
8
+ *
9
+ * @public
10
+ */
11
+ var HieroError = class extends Error {
12
+ /** HTTP status code, if the error originated from an HTTP response. */
13
+ statusCode;
14
+ /** The raw response body, if available. */
15
+ rawBody;
16
+ constructor(message, options) {
17
+ super(message, { cause: options?.cause });
18
+ this.name = "HieroError";
19
+ this.statusCode = options?.statusCode;
20
+ this.rawBody = options?.rawBody;
21
+ Object.setPrototypeOf(this, new.target.prototype);
22
+ }
23
+ toJSON() {
24
+ return {
25
+ name: this.name,
26
+ message: this.message,
27
+ statusCode: this.statusCode
28
+ };
29
+ }
30
+ };
31
+ //#endregion
32
+ //#region src/errors/HieroNotFoundError.ts
33
+ /**
34
+ * Thrown when the server returns HTTP 404 (Not Found) for a specific entity.
35
+ *
36
+ * @public
37
+ */
38
+ var HieroNotFoundError = class extends HieroError {
39
+ /** The entity ID that was not found, if available. */
40
+ entityId;
41
+ constructor(message, options) {
42
+ super(message, {
43
+ statusCode: 404,
44
+ rawBody: options?.rawBody
45
+ });
46
+ this.name = "HieroNotFoundError";
47
+ this.entityId = options?.entityId;
48
+ }
49
+ toJSON() {
50
+ return {
51
+ ...super.toJSON(),
52
+ entityId: this.entityId
53
+ };
54
+ }
55
+ };
56
+ //#endregion
57
+ //#region src/errors/HieroParseError.ts
58
+ /**
59
+ * Thrown when the response body cannot be parsed as JSON,
60
+ * or when the response Content-Type is unexpected (e.g., `text/html`
61
+ * for Unicode parameter errors — EC153).
62
+ *
63
+ * @public
64
+ */
65
+ var HieroParseError = class extends HieroError {
66
+ /** The raw body text that could not be parsed. */
67
+ body;
68
+ constructor(message, options) {
69
+ super(message, {
70
+ statusCode: options.statusCode,
71
+ rawBody: options.body,
72
+ cause: options.cause
73
+ });
74
+ this.name = "HieroParseError";
75
+ this.body = options.body;
76
+ }
77
+ };
78
+ //#endregion
79
+ //#region src/errors/HieroRateLimitError.ts
80
+ /**
81
+ * Thrown when the server returns HTTP 429 (Too Many Requests).
82
+ *
83
+ * @public
84
+ */
85
+ var HieroRateLimitError = class extends HieroError {
86
+ /** Seconds to wait before retrying, parsed from `Retry-After` header. */
87
+ retryAfter;
88
+ constructor(message, options) {
89
+ super(message, {
90
+ statusCode: 429,
91
+ rawBody: options?.rawBody
92
+ });
93
+ this.name = "HieroRateLimitError";
94
+ this.retryAfter = options?.retryAfter;
95
+ }
96
+ toJSON() {
97
+ return {
98
+ ...super.toJSON(),
99
+ retryAfter: this.retryAfter
100
+ };
101
+ }
102
+ };
103
+ //#endregion
104
+ //#region src/errors/HieroServerError.ts
105
+ /**
106
+ * Thrown when the server returns an HTTP 5xx error.
107
+ *
108
+ * @public
109
+ */
110
+ var HieroServerError = class extends HieroError {
111
+ constructor(message, options) {
112
+ super(message, {
113
+ statusCode: options?.statusCode ?? 500,
114
+ rawBody: options?.rawBody
115
+ });
116
+ this.name = "HieroServerError";
117
+ }
118
+ };
119
+ //#endregion
120
+ //#region src/errors/HieroValidationError.ts
121
+ /**
122
+ * Thrown when the server returns HTTP 400 or 415, or when client-side
123
+ * input validation fails before the request is sent.
124
+ *
125
+ * @public
126
+ */
127
+ var HieroValidationError = class extends HieroError {
128
+ /** The specific parameter that caused the validation error, if known. */
129
+ parameter;
130
+ constructor(message, options) {
131
+ super(message, {
132
+ statusCode: options?.statusCode ?? 400,
133
+ rawBody: options?.rawBody
134
+ });
135
+ this.name = "HieroValidationError";
136
+ this.parameter = options?.parameter;
137
+ }
138
+ toJSON() {
139
+ return {
140
+ ...super.toJSON(),
141
+ parameter: this.parameter
142
+ };
143
+ }
144
+ };
145
+ //#endregion
146
+ //#region src/errors/factory.ts
147
+ /**
148
+ * Attempts to extract the error message from the Mirror Node's custom error shape.
149
+ * Falls back to a generic message if parsing fails.
150
+ *
151
+ * @internal
152
+ */
153
+ function extractErrorMessage(body) {
154
+ if (typeof body === "object" && body !== null) {
155
+ const firstMessage = body._status?.messages?.[0];
156
+ if (firstMessage) {
157
+ const parts = [firstMessage.message];
158
+ if (firstMessage.detail) parts.push(firstMessage.detail);
159
+ return parts.filter(Boolean).join(": ");
160
+ }
161
+ }
162
+ return "Unknown error";
163
+ }
164
+ /**
165
+ * Attempts to extract a parameter name from Mirror Node error messages.
166
+ * Messages like "Invalid parameter: limit" → returns "limit".
167
+ *
168
+ * @internal
169
+ */
170
+ function extractParameter(message) {
171
+ return /Invalid parameter:\s*(.+)/i.exec(message)?.[1]?.trim();
172
+ }
173
+ /**
174
+ * Creates the appropriate error subclass from an HTTP response.
175
+ *
176
+ * Maps HTTP status codes to error subclasses:
177
+ * - 400 → `HieroValidationError`
178
+ * - 404 → `HieroNotFoundError`
179
+ * - 415 → `HieroValidationError` (wrong Content-Type, EC52)
180
+ * - 429 → `HieroRateLimitError`
181
+ * - 5xx → `HieroServerError`
182
+ * - Other → `HieroError`
183
+ *
184
+ * @internal
185
+ */
186
+ function createErrorFromResponse(statusCode, body, rawBody, headers) {
187
+ const message = extractErrorMessage(body);
188
+ switch (true) {
189
+ case statusCode === 400:
190
+ case statusCode === 415: return new HieroValidationError(message, {
191
+ statusCode,
192
+ parameter: extractParameter(message),
193
+ rawBody
194
+ });
195
+ case statusCode === 404: return new HieroNotFoundError(message, { rawBody });
196
+ case statusCode === 429: {
197
+ let retryAfter;
198
+ const retryAfterHeader = headers?.get("retry-after");
199
+ if (retryAfterHeader) {
200
+ const parsed = Number.parseInt(retryAfterHeader, 10);
201
+ if (!Number.isNaN(parsed)) retryAfter = parsed;
202
+ }
203
+ return new HieroRateLimitError(message, {
204
+ retryAfter,
205
+ rawBody
206
+ });
207
+ }
208
+ case statusCode >= 500: return new HieroServerError(message, {
209
+ statusCode,
210
+ rawBody
211
+ });
212
+ default: return new HieroError(message, {
213
+ statusCode,
214
+ rawBody
215
+ });
216
+ }
217
+ }
218
+ /**
219
+ * Creates an error from a non-JSON response body (e.g., HTML error pages from EC153).
220
+ *
221
+ * @internal
222
+ */
223
+ function createParseError(rawBody, statusCode, cause) {
224
+ return new HieroParseError("Failed to parse response body as JSON", {
225
+ body: rawBody,
226
+ statusCode,
227
+ cause
228
+ });
229
+ }
230
+ //#endregion
231
+ //#region src/errors/HieroNetworkError.ts
232
+ /**
233
+ * Thrown when a network-level error occurs: DNS resolution failure,
234
+ * connection refused, socket hangup, etc.
235
+ *
236
+ * @public
237
+ */
238
+ var HieroNetworkError = class extends HieroError {
239
+ constructor(message, options) {
240
+ super(message, { cause: options?.cause });
241
+ this.name = "HieroNetworkError";
242
+ }
243
+ };
244
+ //#endregion
245
+ //#region src/errors/HieroTimeoutError.ts
246
+ /**
247
+ * Thrown when a request exceeds the configured timeout (AbortController).
248
+ *
249
+ * @public
250
+ */
251
+ var HieroTimeoutError = class extends HieroError {
252
+ /** The timeout duration in milliseconds that was exceeded. */
253
+ timeoutMs;
254
+ constructor(timeoutMs, options) {
255
+ super(`Request timed out after ${timeoutMs}ms`, { cause: options?.cause });
256
+ this.name = "HieroTimeoutError";
257
+ this.timeoutMs = timeoutMs;
258
+ }
259
+ };
260
+ //#endregion
261
+ //#region src/http/etag-cache.ts
262
+ /**
263
+ * Simple in-memory ETag cache.
264
+ *
265
+ * Keys are normalized URLs (no trailing slashes).
266
+ */
267
+ var ETagCache = class {
268
+ store = /* @__PURE__ */ new Map();
269
+ /**
270
+ * Look up a cached ETag for the given URL.
271
+ *
272
+ * @returns The cached ETag string, or `undefined` if not cached.
273
+ */
274
+ getETag(url) {
275
+ return this.store.get(this.normalizeKey(url))?.etag;
276
+ }
277
+ /**
278
+ * Look up the cached response body for the given URL.
279
+ *
280
+ * @returns The cached body, or `undefined` if not cached.
281
+ */
282
+ getCachedBody(url) {
283
+ return this.store.get(this.normalizeKey(url))?.body;
284
+ }
285
+ /**
286
+ * Store or update a cache entry.
287
+ */
288
+ set(url, etag, body) {
289
+ this.store.set(this.normalizeKey(url), {
290
+ etag,
291
+ body
292
+ });
293
+ }
294
+ /**
295
+ * Remove a cache entry.
296
+ */
297
+ delete(url) {
298
+ this.store.delete(this.normalizeKey(url));
299
+ }
300
+ /**
301
+ * Clear all cached entries.
302
+ */
303
+ clear() {
304
+ this.store.clear();
305
+ }
306
+ /**
307
+ * Number of entries in the cache.
308
+ */
309
+ get size() {
310
+ return this.store.size;
311
+ }
312
+ /**
313
+ * Normalize a URL for use as a cache key.
314
+ *
315
+ * EC43: Removes trailing slashes.
316
+ */
317
+ normalizeKey(url) {
318
+ return url.replace(/\/+$/, "");
319
+ }
320
+ };
321
+ //#endregion
322
+ //#region src/http/json-parser.ts
323
+ /**
324
+ * Safe JSON parser that prevents int64 precision loss.
325
+ *
326
+ * **Primary:** TC39 Stage 4 `context.source` reviver (Node 22+, modern browsers as of March 2026).
327
+ * **Fallback:** `lossless-json` library (4KB, for Node 18-20 without `context.source`).
328
+ *
329
+ * Numbers exceeding `Number.MAX_SAFE_INTEGER` are returned as strings.
330
+ * Decimals (e.g., `0.1`) remain as numbers.
331
+ *
332
+ * @internal
333
+ */
334
+ /** Maximum response body size (10MB) before parsing is rejected. */
335
+ const MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
336
+ /**
337
+ * Feature-detect `context.source` support (TC39 Stage 4, Nov 2025).
338
+ * Evaluated once at module load time.
339
+ */
340
+ const HAS_CONTEXT_SOURCE = (() => {
341
+ try {
342
+ let seen = false;
343
+ JSON.parse("1", (_key, _value, context) => {
344
+ if (context?.source !== void 0) seen = true;
345
+ });
346
+ return seen;
347
+ } catch {
348
+ return false;
349
+ }
350
+ })();
351
+ /**
352
+ * Reviver function for `JSON.parse` that uses TC39 `context.source` to detect
353
+ * unsafe integers and return them as strings.
354
+ *
355
+ * Rules:
356
+ * - Integers exceeding MAX_SAFE_INTEGER → return raw source string
357
+ * - Safe integers → return as number
358
+ * - Decimals (non-integer numbers like 0.1) → return as number
359
+ * - All other types → pass through unchanged
360
+ */
361
+ function safeReviver(_key, value, context) {
362
+ if (typeof value === "number" && context?.source !== void 0) {
363
+ const source = context.source;
364
+ if (!source.includes(".") && !source.includes("e") && !source.includes("E") && !Number.isSafeInteger(value)) return source;
365
+ }
366
+ return value;
367
+ }
368
+ /**
369
+ * Fallback parser using `lossless-json` for environments without `context.source`.
370
+ *
371
+ * The lossless-json library parses all numbers as `LosslessNumber` objects,
372
+ * then the reviver converts them back to `number` (if safe) or `string` (if unsafe).
373
+ */
374
+ function safeJsonParseFallback(text) {
375
+ return parse(text, void 0, (value) => {
376
+ const num = Number(value);
377
+ if (Number.isNaN(num)) return value;
378
+ if (!(!value.includes(".") && !value.includes("e") && !value.includes("E"))) return num;
379
+ if (Number.isSafeInteger(num)) return num;
380
+ return value;
381
+ });
382
+ }
383
+ /**
384
+ * Parses a JSON string safely, preserving precision for large integers.
385
+ *
386
+ * Numbers exceeding `Number.MAX_SAFE_INTEGER` are returned as strings.
387
+ * Decimals remain as JavaScript `number` values.
388
+ *
389
+ * @param text - The raw JSON string to parse
390
+ * @returns The parsed value
391
+ * @throws {Error} If the input exceeds the maximum response size
392
+ * @throws {SyntaxError} If the input is not valid JSON
393
+ *
394
+ * @internal
395
+ */
396
+ function safeJsonParse(text) {
397
+ if (text.length > MAX_RESPONSE_SIZE) throw new Error(`Response body exceeds maximum size of ${MAX_RESPONSE_SIZE} bytes (${text.length} bytes received)`);
398
+ if (HAS_CONTEXT_SOURCE) return JSON.parse(text, safeReviver);
399
+ return safeJsonParseFallback(text);
400
+ }
401
+ //#endregion
402
+ //#region src/http/rate-limiter.ts
403
+ /**
404
+ * Token bucket rate limiter for client-side request throttling.
405
+ *
406
+ * Uses a simple token bucket algorithm:
407
+ * - Bucket starts full with `maxTokens` tokens
408
+ * - Each request consumes 1 token
409
+ * - Tokens replenish at `tokensPerSecond` rate
410
+ * - If no tokens available, `acquire()` waits until a token is available
411
+ *
412
+ * Default: 50 requests per second.
413
+ *
414
+ * @internal
415
+ */
416
+ var RateLimiter = class {
417
+ tokens;
418
+ maxTokens;
419
+ refillRate;
420
+ lastRefillTime;
421
+ constructor(tokensPerSecond = 50) {
422
+ this.maxTokens = tokensPerSecond;
423
+ this.tokens = tokensPerSecond;
424
+ this.refillRate = tokensPerSecond / 1e3;
425
+ this.lastRefillTime = Date.now();
426
+ }
427
+ /**
428
+ * Refills tokens based on elapsed time since last refill.
429
+ */
430
+ refill() {
431
+ const now = Date.now();
432
+ const newTokens = (now - this.lastRefillTime) * this.refillRate;
433
+ this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
434
+ this.lastRefillTime = now;
435
+ }
436
+ /**
437
+ * Acquires a token. If no tokens are available, waits until one is.
438
+ *
439
+ * @param signal - Optional abort signal for cancellation
440
+ * @returns Promise that resolves when a token is acquired
441
+ */
442
+ async acquire(signal) {
443
+ this.refill();
444
+ if (this.tokens >= 1) {
445
+ this.tokens -= 1;
446
+ return;
447
+ }
448
+ const deficit = 1 - this.tokens;
449
+ const waitMs = Math.ceil(deficit / this.refillRate);
450
+ await new Promise((resolve, reject) => {
451
+ if (signal?.aborted) {
452
+ reject(signal.reason);
453
+ return;
454
+ }
455
+ const timer = setTimeout(() => {
456
+ this.refill();
457
+ this.tokens -= 1;
458
+ resolve();
459
+ }, waitMs);
460
+ signal?.addEventListener("abort", () => {
461
+ clearTimeout(timer);
462
+ reject(signal.reason);
463
+ }, { once: true });
464
+ });
465
+ }
466
+ /**
467
+ * Returns the current number of available tokens (for testing).
468
+ *
469
+ * @internal
470
+ */
471
+ get availableTokens() {
472
+ this.refill();
473
+ return this.tokens;
474
+ }
475
+ };
476
+ //#endregion
477
+ //#region src/http/retry.ts
478
+ const DEFAULT_RETRY_OPTIONS = {
479
+ maxRetries: 2,
480
+ baseDelay: 500,
481
+ maxDelay: 1e4
482
+ };
483
+ /**
484
+ * Determines whether a given HTTP status code is retryable.
485
+ *
486
+ * - 429 (Too Many Requests): always retryable
487
+ * - 5xx (Server Error): retryable
488
+ * - 4xx (Client Error): not retryable (except 429)
489
+ *
490
+ * @internal
491
+ */
492
+ function isRetryableStatus(statusCode) {
493
+ if (statusCode === 429) return true;
494
+ if (statusCode >= 500) return true;
495
+ return false;
496
+ }
497
+ /**
498
+ * Determines whether an error is a retryable network error.
499
+ *
500
+ * @internal
501
+ */
502
+ function isRetryableError(error) {
503
+ if (error instanceof TypeError) return true;
504
+ if (error instanceof DOMException && error.name === "AbortError") return false;
505
+ return false;
506
+ }
507
+ /**
508
+ * Computes the delay before the next retry attempt using exponential backoff
509
+ * with full jitter.
510
+ *
511
+ * Formula: `random(0, min(maxDelay, baseDelay * 2^attempt))`
512
+ *
513
+ * @param attempt - Zero-based attempt number (0 = first retry)
514
+ * @param options - Retry configuration
515
+ * @param retryAfter - Optional `Retry-After` value from 429 response (seconds)
516
+ * @returns Delay in milliseconds
517
+ *
518
+ * @internal
519
+ */
520
+ function computeRetryDelay(attempt, options, retryAfter) {
521
+ if (retryAfter !== void 0 && retryAfter > 0) return retryAfter * 1e3;
522
+ const exponentialDelay = options.baseDelay * 2 ** attempt;
523
+ const cappedDelay = Math.min(exponentialDelay, options.maxDelay);
524
+ return Math.random() * cappedDelay;
525
+ }
526
+ /**
527
+ * Sleeps for the specified number of milliseconds.
528
+ * Returns a promise that resolves after the delay, respecting abort signals.
529
+ *
530
+ * @internal
531
+ */
532
+ function sleep(ms, signal) {
533
+ return new Promise((resolve, reject) => {
534
+ if (signal?.aborted) {
535
+ reject(signal.reason);
536
+ return;
537
+ }
538
+ const timer = setTimeout(resolve, ms);
539
+ signal?.addEventListener("abort", () => {
540
+ clearTimeout(timer);
541
+ reject(signal.reason);
542
+ }, { once: true });
543
+ });
544
+ }
545
+ //#endregion
546
+ //#region src/http/url-builder.ts
547
+ /** Maximum URL length before rejection (EC34). */
548
+ const MAX_URL_LENGTH = 4096;
549
+ /**
550
+ * Mapping of SDK-friendly camelCase parameter names to the dot-notation
551
+ * parameter names expected by the Mirror Node API (EC64).
552
+ */
553
+ const PARAM_NAME_MAP = {
554
+ senderId: "sender.id",
555
+ receiverId: "receiver.id",
556
+ accountId: "account.id",
557
+ tokenId: "token.id",
558
+ nodeId: "node.id",
559
+ scheduleId: "schedule.id",
560
+ nonce: "nonce"
561
+ };
562
+ /**
563
+ * Resolves a parameter name through the camelCase→dot.notation map.
564
+ */
565
+ function resolveParamName(name) {
566
+ return PARAM_NAME_MAP[name] ?? name;
567
+ }
568
+ /**
569
+ * Builds a complete URL from base URL, path segments, and query parameters.
570
+ *
571
+ * Handles:
572
+ * - Double-slash collapsing (EC115)
573
+ * - Null byte stripping (EC154)
574
+ * - Trailing slash removal (EC43)
575
+ * - Operator query params: `timestamp=gt:1234` (EC10)
576
+ * - Scalar query params: `limit=10` (EC42)
577
+ * - CamelCase→dot.notation parameter name mapping (EC64)
578
+ * - URL length validation (<4KB, EC34)
579
+ * - Omission of undefined/null/"" param values (EC119)
580
+ *
581
+ * @param baseUrl - The base URL (e.g., `https://testnet.mirrornode.hedera.com`)
582
+ * @param path - The API path (e.g., `/api/v1/accounts`)
583
+ * @param params - Optional query parameters
584
+ * @returns The fully constructed URL string
585
+ * @throws {HieroValidationError} If the resulting URL exceeds the max length
586
+ *
587
+ * @internal
588
+ */
589
+ function buildUrl(baseUrl, path, params) {
590
+ let cleanPath = path.replace(/\0/g, "");
591
+ cleanPath = cleanPath.replace(/(?<!:)\/{2,}/g, "/");
592
+ if (cleanPath.length > 1 && cleanPath.endsWith("/")) cleanPath = cleanPath.slice(0, -1);
593
+ const url = new URL(cleanPath, baseUrl);
594
+ if (params) for (const [key, value] of Object.entries(params)) {
595
+ if (value === void 0 || value === null || value === "") continue;
596
+ const resolvedKey = resolveParamName(key);
597
+ if (typeof value === "object" && "operator" in value) url.searchParams.append(resolvedKey, `${value.operator}:${value.value}`);
598
+ else if (Array.isArray(value)) {
599
+ for (const item of value) if (typeof item === "object" && "operator" in item) url.searchParams.append(resolvedKey, `${item.operator}:${item.value}`);
600
+ } else url.searchParams.set(resolvedKey, String(value));
601
+ }
602
+ const result = url.toString();
603
+ if (result.length > MAX_URL_LENGTH) throw new HieroValidationError(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters (${result.length} chars).`, { parameter: "url" });
604
+ return result;
605
+ }
606
+ //#endregion
607
+ //#region src/http/client.ts
608
+ /**
609
+ * Core HTTP client for the Hiero Mirror Node SDK.
610
+ *
611
+ * Wraps `fetch` with:
612
+ * - Timeout via `AbortController`
613
+ * - Safe JSON parsing (int64 precision)
614
+ * - Error factory integration
615
+ * - Rate limiting
616
+ * - Request deduplication
617
+ * - Retry with exponential backoff
618
+ * - ETag/conditional request support (stubbed for Step 11)
619
+ *
620
+ * @internal
621
+ */
622
+ var HttpClient = class {
623
+ baseUrl;
624
+ timeout;
625
+ retryOptions;
626
+ rateLimiter;
627
+ inflight = /* @__PURE__ */ new Map();
628
+ etagCache;
629
+ logger;
630
+ fetchFn;
631
+ constructor(options) {
632
+ this.baseUrl = options.baseUrl;
633
+ this.timeout = options.timeout ?? 3e4;
634
+ this.retryOptions = {
635
+ ...DEFAULT_RETRY_OPTIONS,
636
+ ...options.retry
637
+ };
638
+ this.rateLimiter = new RateLimiter(options.rateLimitRps ?? 50);
639
+ this.etagCache = new ETagCache();
640
+ this.logger = options.logger ?? {};
641
+ this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
642
+ }
643
+ /**
644
+ * Performs a GET request with in-flight deduplication.
645
+ *
646
+ * @param path - API path (e.g., `/api/v1/accounts`)
647
+ * @param params - Optional query parameters
648
+ * @param options - Optional request options
649
+ * @returns Parsed response
650
+ */
651
+ async get(path, params, options) {
652
+ const url = buildUrl(this.baseUrl, path, params);
653
+ const existing = this.inflight.get(url);
654
+ if (existing) return existing;
655
+ const cachedETag = this.etagCache.getETag(url);
656
+ const mergedOptions = cachedETag ? {
657
+ ...options,
658
+ headers: {
659
+ ...options?.headers,
660
+ "If-None-Match": cachedETag
661
+ }
662
+ } : options ?? {};
663
+ const promise = this.request("GET", url, void 0, mergedOptions);
664
+ this.inflight.set(url, promise);
665
+ try {
666
+ return await promise;
667
+ } finally {
668
+ this.inflight.delete(url);
669
+ }
670
+ }
671
+ /**
672
+ * Performs a POST request.
673
+ *
674
+ * POST requests are NOT deduplicated (they have side effects).
675
+ *
676
+ * @param path - API path
677
+ * @param body - Request body (will be JSON-serialized)
678
+ * @param options - Optional request options
679
+ * @returns Parsed response
680
+ */
681
+ async post(path, body, options) {
682
+ const url = buildUrl(this.baseUrl, path);
683
+ return this.request("POST", url, body, options);
684
+ }
685
+ /**
686
+ * Core request method with retry, rate limiting, timeout, and error handling.
687
+ */
688
+ async request(method, url, body, options) {
689
+ let lastError;
690
+ for (let attempt = 0; attempt <= this.retryOptions.maxRetries; attempt++) try {
691
+ await this.rateLimiter.acquire(options?.signal);
692
+ const timeoutMs = options?.timeout ?? this.timeout;
693
+ const controller = new AbortController();
694
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
695
+ if (options?.signal) options.signal.addEventListener("abort", () => controller.abort(options.signal?.reason), { once: true });
696
+ try {
697
+ const headers = {
698
+ Accept: "application/json",
699
+ "Accept-Encoding": "gzip",
700
+ ...options?.headers
701
+ };
702
+ if (body !== void 0) headers["Content-Type"] = "application/json";
703
+ this.logger.debug?.(`[HTTP] ${method} ${url}`, { attempt });
704
+ const response = await this.fetchFn(url, {
705
+ method,
706
+ headers,
707
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
708
+ signal: controller.signal
709
+ });
710
+ clearTimeout(timeoutId);
711
+ return await this.handleResponse(response, url, attempt);
712
+ } catch (error) {
713
+ clearTimeout(timeoutId);
714
+ if (error instanceof DOMException && error.name === "AbortError" && !options?.signal?.aborted) throw new HieroTimeoutError(timeoutMs);
715
+ throw error;
716
+ }
717
+ } catch (error) {
718
+ lastError = error;
719
+ if (error instanceof HieroTimeoutError) throw error;
720
+ if (options?.signal?.aborted) throw error;
721
+ if (!(isRetryableError(error) || error !== null && typeof error === "object" && "statusCode" in error && isRetryableStatus(error.statusCode)) || attempt >= this.retryOptions.maxRetries) throw error;
722
+ const retryAfter = error !== null && typeof error === "object" && "retryAfter" in error ? error.retryAfter : void 0;
723
+ const delay = computeRetryDelay(attempt, this.retryOptions, retryAfter);
724
+ this.logger.warn?.(`[HTTP] Retry ${attempt + 1}/${this.retryOptions.maxRetries} after ${Math.round(delay)}ms`, { url });
725
+ await sleep(delay, options?.signal);
726
+ }
727
+ if (lastError instanceof Error) throw lastError;
728
+ throw new HieroNetworkError("Request failed after all retries");
729
+ }
730
+ /**
731
+ * Handles the HTTP response: checks content type, parses JSON safely,
732
+ * and throws appropriate errors for non-2xx responses.
733
+ */
734
+ async handleResponse(response, url, _attempt) {
735
+ const rawBody = await response.text();
736
+ if (response.status === 304) {
737
+ const cachedBody = this.etagCache.getCachedBody(url);
738
+ this.logger.debug?.(`[HTTP] 304 Not Modified — returning cached body for ${url}`);
739
+ return {
740
+ data: cachedBody ?? null,
741
+ status: 304,
742
+ headers: response.headers
743
+ };
744
+ }
745
+ const contentType = response.headers.get("content-type") ?? "";
746
+ const isJson = contentType.includes("application/json") || contentType.includes("text/json");
747
+ if (!response.ok) {
748
+ let parsedBody = null;
749
+ if (isJson && rawBody) try {
750
+ parsedBody = JSON.parse(rawBody);
751
+ } catch {}
752
+ if (parsedBody) throw createErrorFromResponse(response.status, parsedBody, rawBody, response.headers);
753
+ throw createParseError(rawBody, response.status);
754
+ }
755
+ if (response.status === 204 || !rawBody) return {
756
+ data: null,
757
+ status: response.status,
758
+ headers: response.headers
759
+ };
760
+ if (!isJson) this.logger.warn?.(`[HTTP] Unexpected content-type "${contentType}" for ${url}`);
761
+ try {
762
+ const data = safeJsonParse(rawBody);
763
+ const etag = response.headers.get("etag");
764
+ if (etag) this.etagCache.set(url, etag, data);
765
+ return {
766
+ data,
767
+ status: response.status,
768
+ headers: response.headers
769
+ };
770
+ } catch (cause) {
771
+ throw createParseError(rawBody, response.status, cause);
772
+ }
773
+ }
774
+ };
775
+ //#endregion
776
+ //#region src/mappers/common.ts
777
+ /**
778
+ * Shared mapper utilities for transforming raw API JSON → SDK types.
779
+ *
780
+ * @internal
781
+ * @packageDocumentation
782
+ */
783
+ /**
784
+ * Decodes a Base64 string to a `Uint8Array`.
785
+ *
786
+ * Uses `atob()` which is available in all modern environments
787
+ * (browsers, Node 16+, Deno, Bun).
788
+ */
789
+ function decodeBase64(value) {
790
+ const binaryString = atob(value);
791
+ const bytes = new Uint8Array(binaryString.length);
792
+ for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
793
+ return bytes;
794
+ }
795
+ /**
796
+ * Decodes a hex-encoded string (e.g., `0x57524f4e475f4e4f4e4345`) to UTF-8.
797
+ *
798
+ * EC24: Contract `error_message` fields are hex-encoded.
799
+ *
800
+ * @returns The decoded string, or `null` if the input is not valid hex.
801
+ */
802
+ function decodeHexString(hex) {
803
+ if (hex == null || hex === "") return null;
804
+ const raw = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
805
+ if (raw.length === 0 || raw.length % 2 !== 0) return null;
806
+ try {
807
+ const bytes = [];
808
+ for (let i = 0; i < raw.length; i += 2) bytes.push(Number.parseInt(raw.substring(i, i + 2), 16));
809
+ return new TextDecoder().decode(new Uint8Array(bytes));
810
+ } catch {
811
+ return null;
812
+ }
813
+ }
814
+ /**
815
+ * Coerces a value to a string representation.
816
+ *
817
+ * EC14/88: `decimals` is sometimes returned as a number (list endpoints)
818
+ * and sometimes as a string (detail endpoints). We normalize to string.
819
+ */
820
+ function ensureString(value) {
821
+ if (value == null) return "0";
822
+ return String(value);
823
+ }
824
+ /**
825
+ * Casts unknown to a record for field extraction.
826
+ *
827
+ * @internal
828
+ */
829
+ function asRecord(value) {
830
+ if (value != null && typeof value === "object" && !Array.isArray(value)) return value;
831
+ return {};
832
+ }
833
+ /**
834
+ * Extracts a string field with optional fallback.
835
+ */
836
+ function str(raw, key, fallback = null) {
837
+ const v = raw[key];
838
+ if (v == null) return fallback;
839
+ return String(v);
840
+ }
841
+ /**
842
+ * Extracts a required string field (returns empty string if missing).
843
+ */
844
+ function strReq(raw, key) {
845
+ return str(raw, key, "");
846
+ }
847
+ /**
848
+ * Extracts a number field with optional fallback.
849
+ */
850
+ function num(raw, key, fallback = 0) {
851
+ const v = raw[key];
852
+ if (v == null) return fallback;
853
+ return typeof v === "number" ? v : Number(v);
854
+ }
855
+ /**
856
+ * Extracts a boolean field.
857
+ */
858
+ function bool(raw, key, fallback = false) {
859
+ const v = raw[key];
860
+ if (v == null) return fallback;
861
+ return Boolean(v);
862
+ }
863
+ /**
864
+ * Extracts an array field.
865
+ */
866
+ function arr(raw, key) {
867
+ const v = raw[key];
868
+ return Array.isArray(v) ? v : [];
869
+ }
870
+ //#endregion
871
+ //#region src/mappers/account.ts
872
+ function mapKey$3(raw) {
873
+ if (raw == null) return null;
874
+ const r = asRecord(raw);
875
+ return {
876
+ _type: strReq(r, "_type"),
877
+ key: strReq(r, "key")
878
+ };
879
+ }
880
+ function mapTokenBalance(raw) {
881
+ const r = asRecord(raw);
882
+ return {
883
+ token_id: strReq(r, "token_id"),
884
+ balance: strReq(r, "balance")
885
+ };
886
+ }
887
+ function mapAccountBalance(raw) {
888
+ const r = asRecord(raw);
889
+ return {
890
+ balance: strReq(r, "balance"),
891
+ timestamp: strReq(r, "timestamp"),
892
+ tokens: arr(r, "tokens").map(mapTokenBalance)
893
+ };
894
+ }
895
+ function mapAccountSummary(raw) {
896
+ const r = asRecord(raw);
897
+ return {
898
+ account: strReq(r, "account"),
899
+ alias: str(r, "alias"),
900
+ auto_renew_period: str(r, "auto_renew_period"),
901
+ balance: mapAccountBalance(r.balance),
902
+ created_timestamp: str(r, "created_timestamp"),
903
+ decline_reward: bool(r, "decline_reward"),
904
+ deleted: bool(r, "deleted"),
905
+ ethereum_nonce: strReq(r, "ethereum_nonce"),
906
+ evm_address: str(r, "evm_address"),
907
+ expiry_timestamp: str(r, "expiry_timestamp"),
908
+ key: mapKey$3(r.key),
909
+ max_automatic_token_associations: num(r, "max_automatic_token_associations"),
910
+ memo: strReq(r, "memo"),
911
+ pending_reward: strReq(r, "pending_reward"),
912
+ receiver_sig_required: r.receiver_sig_required == null ? null : bool(r, "receiver_sig_required"),
913
+ staked_account_id: str(r, "staked_account_id"),
914
+ staked_node_id: r.staked_node_id == null ? null : num(r, "staked_node_id"),
915
+ stake_period_start: str(r, "stake_period_start")
916
+ };
917
+ }
918
+ function mapAccountDetail(raw) {
919
+ const r = asRecord(raw);
920
+ const summary = mapAccountSummary(raw);
921
+ const linksRaw = asRecord(r.links);
922
+ return {
923
+ ...summary,
924
+ transactions: arr(r, "transactions").map(mapAccountTransaction),
925
+ links: { next: str(linksRaw, "next") }
926
+ };
927
+ }
928
+ function mapAccountTransaction(raw) {
929
+ const r = asRecord(raw);
930
+ return {
931
+ bytes: str(r, "bytes"),
932
+ charged_tx_fee: strReq(r, "charged_tx_fee"),
933
+ consensus_timestamp: strReq(r, "consensus_timestamp"),
934
+ entity_id: str(r, "entity_id"),
935
+ max_fee: strReq(r, "max_fee"),
936
+ memo_base64: strReq(r, "memo_base64"),
937
+ name: strReq(r, "name"),
938
+ nft_transfers: arr(r, "nft_transfers").map((t) => {
939
+ const tr = asRecord(t);
940
+ return {
941
+ is_approval: bool(tr, "is_approval"),
942
+ receiver_account_id: str(tr, "receiver_account_id"),
943
+ sender_account_id: str(tr, "sender_account_id"),
944
+ serial_number: strReq(tr, "serial_number"),
945
+ token_id: strReq(tr, "token_id")
946
+ };
947
+ }),
948
+ node: str(r, "node"),
949
+ nonce: num(r, "nonce"),
950
+ parent_consensus_timestamp: str(r, "parent_consensus_timestamp"),
951
+ result: strReq(r, "result"),
952
+ scheduled: bool(r, "scheduled"),
953
+ staking_reward_transfers: arr(r, "staking_reward_transfers").map((s) => {
954
+ const sr = asRecord(s);
955
+ return {
956
+ account: strReq(sr, "account"),
957
+ amount: strReq(sr, "amount")
958
+ };
959
+ }),
960
+ token_transfers: arr(r, "token_transfers").map((t) => {
961
+ const tr = asRecord(t);
962
+ return {
963
+ account: strReq(tr, "account"),
964
+ amount: strReq(tr, "amount"),
965
+ is_approval: bool(tr, "is_approval"),
966
+ token_id: strReq(tr, "token_id")
967
+ };
968
+ }),
969
+ transaction_hash: strReq(r, "transaction_hash"),
970
+ transaction_id: strReq(r, "transaction_id"),
971
+ transfers: arr(r, "transfers").map((t) => {
972
+ const tr = asRecord(t);
973
+ return {
974
+ account: strReq(tr, "account"),
975
+ amount: strReq(tr, "amount"),
976
+ is_approval: bool(tr, "is_approval")
977
+ };
978
+ }),
979
+ valid_duration_seconds: strReq(r, "valid_duration_seconds"),
980
+ valid_start_timestamp: strReq(r, "valid_start_timestamp")
981
+ };
982
+ }
983
+ function mapTokenRelationship(raw) {
984
+ const r = asRecord(raw);
985
+ return {
986
+ automatic_association: bool(r, "automatic_association"),
987
+ balance: strReq(r, "balance"),
988
+ created_timestamp: strReq(r, "created_timestamp"),
989
+ decimals: String(r.decimals ?? "0"),
990
+ freeze_status: strReq(r, "freeze_status"),
991
+ kyc_status: strReq(r, "kyc_status"),
992
+ token_id: strReq(r, "token_id")
993
+ };
994
+ }
995
+ function mapStakingReward(raw) {
996
+ const r = asRecord(raw);
997
+ return {
998
+ account_id: strReq(r, "account_id"),
999
+ amount: strReq(r, "amount"),
1000
+ timestamp: strReq(r, "timestamp")
1001
+ };
1002
+ }
1003
+ function mapTimestampRange$2(raw) {
1004
+ const r = asRecord(raw);
1005
+ return {
1006
+ from: strReq(r, "from"),
1007
+ to: str(r, "to")
1008
+ };
1009
+ }
1010
+ function mapCryptoAllowance(raw) {
1011
+ const r = asRecord(raw);
1012
+ return {
1013
+ amount: strReq(r, "amount"),
1014
+ amount_granted: strReq(r, "amount_granted"),
1015
+ owner: strReq(r, "owner"),
1016
+ spender: strReq(r, "spender"),
1017
+ timestamp: mapTimestampRange$2(r.timestamp)
1018
+ };
1019
+ }
1020
+ function mapTokenAllowance(raw) {
1021
+ const r = asRecord(raw);
1022
+ return {
1023
+ amount: strReq(r, "amount"),
1024
+ amount_granted: strReq(r, "amount_granted"),
1025
+ owner: strReq(r, "owner"),
1026
+ spender: strReq(r, "spender"),
1027
+ token_id: strReq(r, "token_id"),
1028
+ timestamp: mapTimestampRange$2(r.timestamp)
1029
+ };
1030
+ }
1031
+ function mapNftAllowance(raw) {
1032
+ const r = asRecord(raw);
1033
+ return {
1034
+ approved_for_all: bool(r, "approved_for_all"),
1035
+ owner: strReq(r, "owner"),
1036
+ spender: strReq(r, "spender"),
1037
+ token_id: strReq(r, "token_id"),
1038
+ timestamp: mapTimestampRange$2(r.timestamp)
1039
+ };
1040
+ }
1041
+ function mapAirdrop(raw) {
1042
+ const r = asRecord(raw);
1043
+ return {
1044
+ amount: strReq(r, "amount"),
1045
+ receiver_id: strReq(r, "receiver_id"),
1046
+ sender_id: strReq(r, "sender_id"),
1047
+ serial_number: str(r, "serial_number"),
1048
+ token_id: strReq(r, "token_id"),
1049
+ timestamp: mapTimestampRange$2(r.timestamp)
1050
+ };
1051
+ }
1052
+ //#endregion
1053
+ //#region src/mappers/token.ts
1054
+ function mapKey$2(raw) {
1055
+ if (raw == null) return null;
1056
+ const r = asRecord(raw);
1057
+ return {
1058
+ _type: strReq(r, "_type"),
1059
+ key: strReq(r, "key")
1060
+ };
1061
+ }
1062
+ function mapFractionAmount(raw) {
1063
+ const r = asRecord(raw);
1064
+ return {
1065
+ numerator: num(r, "numerator"),
1066
+ denominator: num(r, "denominator")
1067
+ };
1068
+ }
1069
+ function mapFixedFee(raw) {
1070
+ const r = asRecord(raw);
1071
+ return {
1072
+ all_collectors_are_exempt: bool(r, "all_collectors_are_exempt"),
1073
+ amount: strReq(r, "amount"),
1074
+ collector_account_id: strReq(r, "collector_account_id"),
1075
+ denominating_token_id: str(r, "denominating_token_id")
1076
+ };
1077
+ }
1078
+ function mapFractionalFee(raw) {
1079
+ const r = asRecord(raw);
1080
+ return {
1081
+ all_collectors_are_exempt: bool(r, "all_collectors_are_exempt"),
1082
+ amount: mapFractionAmount(r.amount),
1083
+ collector_account_id: strReq(r, "collector_account_id"),
1084
+ denominating_token_id: str(r, "denominating_token_id"),
1085
+ maximum: str(r, "maximum"),
1086
+ minimum: strReq(r, "minimum"),
1087
+ net_of_transfers: bool(r, "net_of_transfers")
1088
+ };
1089
+ }
1090
+ function mapRoyaltyFee(raw) {
1091
+ const r = asRecord(raw);
1092
+ return {
1093
+ all_collectors_are_exempt: bool(r, "all_collectors_are_exempt"),
1094
+ amount: mapFractionAmount(r.amount),
1095
+ collector_account_id: strReq(r, "collector_account_id"),
1096
+ fallback_fee: r.fallback_fee != null ? mapFixedFee(r.fallback_fee) : null
1097
+ };
1098
+ }
1099
+ function mapCustomFees(raw) {
1100
+ const r = asRecord(raw);
1101
+ return {
1102
+ created_timestamp: strReq(r, "created_timestamp"),
1103
+ fixed_fees: arr(r, "fixed_fees").map(mapFixedFee),
1104
+ fractional_fees: arr(r, "fractional_fees").map(mapFractionalFee),
1105
+ royalty_fees: arr(r, "royalty_fees").map(mapRoyaltyFee)
1106
+ };
1107
+ }
1108
+ function mapTokenSummary(raw) {
1109
+ const r = asRecord(raw);
1110
+ return {
1111
+ admin_key: mapKey$2(r.admin_key),
1112
+ decimals: ensureString(r.decimals),
1113
+ metadata: strReq(r, "metadata"),
1114
+ name: strReq(r, "name"),
1115
+ symbol: strReq(r, "symbol"),
1116
+ token_id: strReq(r, "token_id"),
1117
+ type: strReq(r, "type")
1118
+ };
1119
+ }
1120
+ function mapTokenDetail(raw) {
1121
+ const r = asRecord(raw);
1122
+ return {
1123
+ ...mapTokenSummary(raw),
1124
+ auto_renew_account: str(r, "auto_renew_account"),
1125
+ auto_renew_period: str(r, "auto_renew_period"),
1126
+ created_timestamp: strReq(r, "created_timestamp"),
1127
+ custom_fees: mapCustomFees(r.custom_fees),
1128
+ deleted: bool(r, "deleted"),
1129
+ expiry_timestamp: str(r, "expiry_timestamp"),
1130
+ fee_schedule_key: mapKey$2(r.fee_schedule_key),
1131
+ freeze_default: bool(r, "freeze_default"),
1132
+ freeze_key: mapKey$2(r.freeze_key),
1133
+ initial_supply: strReq(r, "initial_supply"),
1134
+ kyc_key: mapKey$2(r.kyc_key),
1135
+ max_supply: strReq(r, "max_supply"),
1136
+ memo: strReq(r, "memo"),
1137
+ metadata_key: mapKey$2(r.metadata_key),
1138
+ modified_timestamp: strReq(r, "modified_timestamp"),
1139
+ pause_key: mapKey$2(r.pause_key),
1140
+ pause_status: strReq(r, "pause_status"),
1141
+ supply_key: mapKey$2(r.supply_key),
1142
+ supply_type: strReq(r, "supply_type"),
1143
+ total_supply: strReq(r, "total_supply"),
1144
+ treasury_account_id: strReq(r, "treasury_account_id"),
1145
+ wipe_key: mapKey$2(r.wipe_key)
1146
+ };
1147
+ }
1148
+ function mapTokenNft(raw) {
1149
+ const r = asRecord(raw);
1150
+ return {
1151
+ account_id: strReq(r, "account_id"),
1152
+ created_timestamp: strReq(r, "created_timestamp"),
1153
+ delegating_spender: str(r, "delegating_spender"),
1154
+ deleted: bool(r, "deleted"),
1155
+ metadata: strReq(r, "metadata"),
1156
+ modified_timestamp: strReq(r, "modified_timestamp"),
1157
+ serial_number: strReq(r, "serial_number"),
1158
+ spender: str(r, "spender"),
1159
+ token_id: strReq(r, "token_id")
1160
+ };
1161
+ }
1162
+ function mapTokenBalanceEntry(raw) {
1163
+ const r = asRecord(raw);
1164
+ return {
1165
+ account: strReq(r, "account"),
1166
+ balance: strReq(r, "balance"),
1167
+ decimals: ensureString(r.decimals)
1168
+ };
1169
+ }
1170
+ //#endregion
1171
+ //#region src/pagination/paginator.ts
1172
+ /**
1173
+ * Lazy paginator that fetches pages on demand.
1174
+ *
1175
+ * Implements `PromiseLike<Page<T>>` so `await` returns the first page.
1176
+ * Implements `AsyncIterable<T>` so `for await...of` yields items.
1177
+ *
1178
+ * EC19: Uses `links.next` as an opaque cursor (never constructs cursors).
1179
+ * EC47: Terminates when `links.next` is null.
1180
+ * EC8: Resolves relative `links.next` URLs against the base URL.
1181
+ */
1182
+ var Paginator = class {
1183
+ client;
1184
+ initialPath;
1185
+ params;
1186
+ extract;
1187
+ constructor(options) {
1188
+ this.client = options.client;
1189
+ this.initialPath = options.path;
1190
+ this.params = options.params;
1191
+ this.extract = options.extract;
1192
+ }
1193
+ then(onfulfilled, onrejected) {
1194
+ return this.fetchFirstPage().then(onfulfilled, onrejected);
1195
+ }
1196
+ async *[Symbol.asyncIterator]() {
1197
+ for await (const page of this.pages()) yield* page.data;
1198
+ }
1199
+ /**
1200
+ * Returns an async iterable of page objects.
1201
+ *
1202
+ * Follows `links.next` until null (EC47).
1203
+ */
1204
+ async *pages() {
1205
+ let nextPath = this.initialPath;
1206
+ let isFirst = true;
1207
+ while (nextPath != null) {
1208
+ const response = isFirst ? await this.client.get(nextPath, this.params) : await this.client.get(nextPath);
1209
+ isFirst = false;
1210
+ const page = this.extract(response.data);
1211
+ if (page.links.next == null) {
1212
+ const linkHeader = response.headers.get("link") ?? response.headers.get("Link");
1213
+ if (linkHeader) {
1214
+ const match = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
1215
+ if (match?.[1]) page.links.next = match[1];
1216
+ }
1217
+ }
1218
+ yield page;
1219
+ nextPath = page.links.next;
1220
+ }
1221
+ }
1222
+ async fetchFirstPage() {
1223
+ const response = await this.client.get(this.initialPath, this.params);
1224
+ return this.extract(response.data);
1225
+ }
1226
+ };
1227
+ /**
1228
+ * Creates a standard page extractor for the common envelope:
1229
+ * `{ <key>: T[], links: { next: string | null } }`
1230
+ */
1231
+ function createPageExtractor(dataKey, mapItem) {
1232
+ return (raw) => {
1233
+ const record = raw != null && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
1234
+ const items = Array.isArray(record[dataKey]) ? record[dataKey].map(mapItem) : [];
1235
+ const linksRaw = record.links != null && typeof record.links === "object" ? record.links : {};
1236
+ return {
1237
+ data: items,
1238
+ links: { next: typeof linksRaw.next === "string" ? linksRaw.next : null }
1239
+ };
1240
+ };
1241
+ }
1242
+ //#endregion
1243
+ //#region src/resources/accounts.ts
1244
+ var AccountsResource = class {
1245
+ constructor(client) {
1246
+ this.client = client;
1247
+ }
1248
+ list(params) {
1249
+ return new Paginator({
1250
+ client: this.client,
1251
+ path: "/api/v1/accounts",
1252
+ params,
1253
+ extract: createPageExtractor("accounts", mapAccountSummary)
1254
+ });
1255
+ }
1256
+ async get(idOrAliasOrEvmAddress) {
1257
+ return mapAccountDetail((await this.client.get(`/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}`)).data);
1258
+ }
1259
+ getNFTs(idOrAliasOrEvmAddress, params) {
1260
+ return new Paginator({
1261
+ client: this.client,
1262
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/nfts`,
1263
+ params,
1264
+ extract: createPageExtractor("nfts", mapTokenNft)
1265
+ });
1266
+ }
1267
+ getTokens(idOrAliasOrEvmAddress, params) {
1268
+ return new Paginator({
1269
+ client: this.client,
1270
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/tokens`,
1271
+ params,
1272
+ extract: createPageExtractor("tokens", mapTokenRelationship)
1273
+ });
1274
+ }
1275
+ getRewards(idOrAliasOrEvmAddress, params) {
1276
+ return new Paginator({
1277
+ client: this.client,
1278
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/rewards`,
1279
+ params,
1280
+ extract: createPageExtractor("rewards", mapStakingReward)
1281
+ });
1282
+ }
1283
+ getCryptoAllowances(idOrAliasOrEvmAddress, params) {
1284
+ return new Paginator({
1285
+ client: this.client,
1286
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/allowances/crypto`,
1287
+ params,
1288
+ extract: createPageExtractor("allowances", mapCryptoAllowance)
1289
+ });
1290
+ }
1291
+ getTokenAllowances(idOrAliasOrEvmAddress, params) {
1292
+ return new Paginator({
1293
+ client: this.client,
1294
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/allowances/tokens`,
1295
+ params,
1296
+ extract: createPageExtractor("allowances", mapTokenAllowance)
1297
+ });
1298
+ }
1299
+ getNftAllowances(idOrAliasOrEvmAddress, params) {
1300
+ return new Paginator({
1301
+ client: this.client,
1302
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/allowances/nfts`,
1303
+ params,
1304
+ extract: createPageExtractor("allowances", mapNftAllowance)
1305
+ });
1306
+ }
1307
+ getOutstandingAirdrops(idOrAliasOrEvmAddress, params) {
1308
+ return new Paginator({
1309
+ client: this.client,
1310
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/airdrops/outstanding`,
1311
+ params,
1312
+ extract: createPageExtractor("airdrops", mapAirdrop)
1313
+ });
1314
+ }
1315
+ getPendingAirdrops(idOrAliasOrEvmAddress, params) {
1316
+ return new Paginator({
1317
+ client: this.client,
1318
+ path: `/api/v1/accounts/${encodeURIComponent(idOrAliasOrEvmAddress)}/airdrops/pending`,
1319
+ params,
1320
+ extract: createPageExtractor("airdrops", mapAirdrop)
1321
+ });
1322
+ }
1323
+ };
1324
+ //#endregion
1325
+ //#region src/resources/balances.ts
1326
+ function mapBalanceEntry(raw) {
1327
+ const r = asRecord(raw);
1328
+ return {
1329
+ account: strReq(r, "account"),
1330
+ balance: strReq(r, "balance"),
1331
+ tokens: arr(r, "tokens").map((t) => {
1332
+ const tr = asRecord(t);
1333
+ return {
1334
+ token_id: strReq(tr, "token_id"),
1335
+ balance: strReq(tr, "balance")
1336
+ };
1337
+ })
1338
+ };
1339
+ }
1340
+ var BalancesResource = class {
1341
+ constructor(client) {
1342
+ this.client = client;
1343
+ }
1344
+ list(params) {
1345
+ return new Paginator({
1346
+ client: this.client,
1347
+ path: "/api/v1/balances",
1348
+ params,
1349
+ extract: createPageExtractor("balances", mapBalanceEntry)
1350
+ });
1351
+ }
1352
+ };
1353
+ //#endregion
1354
+ //#region src/mappers/block.ts
1355
+ function mapBlock(raw) {
1356
+ const r = asRecord(raw);
1357
+ const tsRaw = asRecord(r.timestamp);
1358
+ return {
1359
+ count: num(r, "count"),
1360
+ gas_used: strReq(r, "gas_used"),
1361
+ hapi_version: strReq(r, "hapi_version"),
1362
+ hash: strReq(r, "hash"),
1363
+ logs_bloom: strReq(r, "logs_bloom"),
1364
+ name: strReq(r, "name"),
1365
+ number: num(r, "number"),
1366
+ previous_hash: strReq(r, "previous_hash"),
1367
+ size: num(r, "size"),
1368
+ timestamp: {
1369
+ from: strReq(tsRaw, "from"),
1370
+ to: str(tsRaw, "to")
1371
+ }
1372
+ };
1373
+ }
1374
+ //#endregion
1375
+ //#region src/resources/blocks.ts
1376
+ var BlocksResource = class {
1377
+ constructor(client) {
1378
+ this.client = client;
1379
+ }
1380
+ list(params) {
1381
+ return new Paginator({
1382
+ client: this.client,
1383
+ path: "/api/v1/blocks",
1384
+ params,
1385
+ extract: createPageExtractor("blocks", mapBlock)
1386
+ });
1387
+ }
1388
+ async get(hashOrNumber) {
1389
+ return mapBlock((await this.client.get(`/api/v1/blocks/${encodeURIComponent(String(hashOrNumber))}`)).data);
1390
+ }
1391
+ };
1392
+ //#endregion
1393
+ //#region src/mappers/contract.ts
1394
+ function mapKey$1(raw) {
1395
+ if (raw == null) return null;
1396
+ const r = asRecord(raw);
1397
+ return {
1398
+ _type: strReq(r, "_type"),
1399
+ key: strReq(r, "key")
1400
+ };
1401
+ }
1402
+ function mapTimestampRange$1(raw) {
1403
+ const r = asRecord(raw);
1404
+ return {
1405
+ from: strReq(r, "from"),
1406
+ to: str(r, "to")
1407
+ };
1408
+ }
1409
+ function mapContractSummary(raw) {
1410
+ const r = asRecord(raw);
1411
+ return {
1412
+ admin_key: mapKey$1(r.admin_key),
1413
+ auto_renew_account: str(r, "auto_renew_account"),
1414
+ auto_renew_period: str(r, "auto_renew_period"),
1415
+ contract_id: strReq(r, "contract_id"),
1416
+ created_timestamp: strReq(r, "created_timestamp"),
1417
+ deleted: bool(r, "deleted"),
1418
+ evm_address: strReq(r, "evm_address"),
1419
+ expiration_timestamp: str(r, "expiration_timestamp"),
1420
+ file_id: str(r, "file_id"),
1421
+ max_automatic_token_associations: num(r, "max_automatic_token_associations"),
1422
+ memo: strReq(r, "memo"),
1423
+ nonce: strReq(r, "nonce"),
1424
+ obtainer_id: str(r, "obtainer_id"),
1425
+ permanent_removal: r.permanent_removal == null ? null : bool(r, "permanent_removal"),
1426
+ proxy_account_id: str(r, "proxy_account_id"),
1427
+ timestamp: mapTimestampRange$1(r.timestamp)
1428
+ };
1429
+ }
1430
+ function mapContractDetail(raw) {
1431
+ const r = asRecord(raw);
1432
+ return {
1433
+ ...mapContractSummary(raw),
1434
+ bytecode: strReq(r, "bytecode"),
1435
+ runtime_bytecode: strReq(r, "runtime_bytecode")
1436
+ };
1437
+ }
1438
+ function mapContractLog(raw) {
1439
+ const r = asRecord(raw);
1440
+ return {
1441
+ address: strReq(r, "address"),
1442
+ bloom: strReq(r, "bloom"),
1443
+ contract_id: strReq(r, "contract_id"),
1444
+ data: strReq(r, "data"),
1445
+ index: num(r, "index"),
1446
+ topics: arr(r, "topics"),
1447
+ root_contract_id: str(r, "root_contract_id"),
1448
+ timestamp: strReq(r, "timestamp"),
1449
+ block_hash: strReq(r, "block_hash"),
1450
+ block_number: num(r, "block_number"),
1451
+ transaction_hash: strReq(r, "transaction_hash"),
1452
+ transaction_index: num(r, "transaction_index")
1453
+ };
1454
+ }
1455
+ function mapStateChange$1(raw) {
1456
+ const r = asRecord(raw);
1457
+ return {
1458
+ address: strReq(r, "address"),
1459
+ contract_id: strReq(r, "contract_id"),
1460
+ slot: strReq(r, "slot"),
1461
+ value_read: strReq(r, "value_read"),
1462
+ value_written: str(r, "value_written")
1463
+ };
1464
+ }
1465
+ function mapContractResult(raw) {
1466
+ const r = asRecord(raw);
1467
+ const errorMessage = str(r, "error_message");
1468
+ return {
1469
+ access_list: str(r, "access_list"),
1470
+ address: strReq(r, "address"),
1471
+ amount: strReq(r, "amount"),
1472
+ block_gas_used: strReq(r, "block_gas_used"),
1473
+ block_hash: strReq(r, "block_hash"),
1474
+ block_number: num(r, "block_number"),
1475
+ bloom: strReq(r, "bloom"),
1476
+ call_result: str(r, "call_result"),
1477
+ chain_id: strReq(r, "chain_id"),
1478
+ contract_id: str(r, "contract_id"),
1479
+ created_contract_ids: arr(r, "created_contract_ids"),
1480
+ error_message: errorMessage,
1481
+ error_message_decoded: decodeHexString(errorMessage),
1482
+ failed_initcode: str(r, "failed_initcode"),
1483
+ from: strReq(r, "from"),
1484
+ function_parameters: strReq(r, "function_parameters"),
1485
+ gas_consumed: str(r, "gas_consumed"),
1486
+ gas_limit: strReq(r, "gas_limit"),
1487
+ gas_price: strReq(r, "gas_price"),
1488
+ gas_used: strReq(r, "gas_used"),
1489
+ hash: strReq(r, "hash"),
1490
+ logs: arr(r, "logs").map(mapContractLog),
1491
+ max_fee_per_gas: strReq(r, "max_fee_per_gas"),
1492
+ max_priority_fee_per_gas: strReq(r, "max_priority_fee_per_gas"),
1493
+ nonce: num(r, "nonce"),
1494
+ r: strReq(r, "r"),
1495
+ result: strReq(r, "result"),
1496
+ s: strReq(r, "s"),
1497
+ state_changes: arr(r, "state_changes").map(mapStateChange$1),
1498
+ status: strReq(r, "status"),
1499
+ timestamp: strReq(r, "timestamp"),
1500
+ to: str(r, "to"),
1501
+ transaction_index: num(r, "transaction_index"),
1502
+ type: num(r, "type"),
1503
+ v: num(r, "v")
1504
+ };
1505
+ }
1506
+ function mapContractAction(raw) {
1507
+ const r = asRecord(raw);
1508
+ return {
1509
+ call_depth: num(r, "call_depth"),
1510
+ call_operation_type: strReq(r, "call_operation_type"),
1511
+ call_type: strReq(r, "call_type"),
1512
+ caller: strReq(r, "caller"),
1513
+ caller_type: strReq(r, "caller_type"),
1514
+ from: strReq(r, "from"),
1515
+ gas: strReq(r, "gas"),
1516
+ gas_used: strReq(r, "gas_used"),
1517
+ index: num(r, "index"),
1518
+ input: str(r, "input"),
1519
+ recipient: str(r, "recipient"),
1520
+ recipient_type: str(r, "recipient_type"),
1521
+ result_data: str(r, "result_data"),
1522
+ result_data_type: strReq(r, "result_data_type"),
1523
+ timestamp: strReq(r, "timestamp"),
1524
+ to: str(r, "to"),
1525
+ value: strReq(r, "value")
1526
+ };
1527
+ }
1528
+ //#endregion
1529
+ //#region src/resources/contracts.ts
1530
+ function mapStateChange(raw) {
1531
+ const r = asRecord(raw);
1532
+ return {
1533
+ address: strReq(r, "address"),
1534
+ contract_id: strReq(r, "contract_id"),
1535
+ slot: strReq(r, "slot"),
1536
+ value_read: strReq(r, "value_read"),
1537
+ value_written: r.value_written == null ? null : strReq(r, "value_written")
1538
+ };
1539
+ }
1540
+ var ContractsResource = class {
1541
+ constructor(client) {
1542
+ this.client = client;
1543
+ }
1544
+ list(params) {
1545
+ return new Paginator({
1546
+ client: this.client,
1547
+ path: "/api/v1/contracts",
1548
+ params,
1549
+ extract: createPageExtractor("contracts", mapContractSummary)
1550
+ });
1551
+ }
1552
+ async get(contractIdOrAddress) {
1553
+ return mapContractDetail((await this.client.get(`/api/v1/contracts/${encodeURIComponent(contractIdOrAddress)}`)).data);
1554
+ }
1555
+ /**
1556
+ * POST /api/v1/contracts/call — smart contract read-only simulation.
1557
+ *
1558
+ * EC29/51/130-133.
1559
+ */
1560
+ async call(request) {
1561
+ return { result: strReq(asRecord((await this.client.post("/api/v1/contracts/call", request)).data), "result") };
1562
+ }
1563
+ getResults(params) {
1564
+ return new Paginator({
1565
+ client: this.client,
1566
+ path: "/api/v1/contracts/results",
1567
+ params,
1568
+ extract: createPageExtractor("results", mapContractResult)
1569
+ });
1570
+ }
1571
+ getResultsByContract(contractIdOrAddress, params) {
1572
+ return new Paginator({
1573
+ client: this.client,
1574
+ path: `/api/v1/contracts/${encodeURIComponent(contractIdOrAddress)}/results`,
1575
+ params,
1576
+ extract: createPageExtractor("results", mapContractResult)
1577
+ });
1578
+ }
1579
+ async getResultByTimestamp(contractIdOrAddress, timestamp) {
1580
+ return mapContractResult((await this.client.get(`/api/v1/contracts/${encodeURIComponent(contractIdOrAddress)}/results/${encodeURIComponent(timestamp)}`)).data);
1581
+ }
1582
+ async getResultByTransactionIdOrHash(transactionIdOrHash) {
1583
+ return mapContractResult((await this.client.get(`/api/v1/contracts/results/${encodeURIComponent(transactionIdOrHash)}`)).data);
1584
+ }
1585
+ getActions(transactionIdOrHash) {
1586
+ return new Paginator({
1587
+ client: this.client,
1588
+ path: `/api/v1/contracts/results/${encodeURIComponent(transactionIdOrHash)}/actions`,
1589
+ extract: createPageExtractor("actions", mapContractAction)
1590
+ });
1591
+ }
1592
+ getLogs(params) {
1593
+ return new Paginator({
1594
+ client: this.client,
1595
+ path: "/api/v1/contracts/results/logs",
1596
+ params,
1597
+ extract: createPageExtractor("logs", mapContractLog)
1598
+ });
1599
+ }
1600
+ getLogsByContract(contractIdOrAddress, params) {
1601
+ return new Paginator({
1602
+ client: this.client,
1603
+ path: `/api/v1/contracts/${encodeURIComponent(contractIdOrAddress)}/results/logs`,
1604
+ params,
1605
+ extract: createPageExtractor("logs", mapContractLog)
1606
+ });
1607
+ }
1608
+ getState(contractIdOrAddress, params) {
1609
+ return new Paginator({
1610
+ client: this.client,
1611
+ path: `/api/v1/contracts/${encodeURIComponent(contractIdOrAddress)}/state`,
1612
+ params,
1613
+ extract: createPageExtractor("state", mapStateChange)
1614
+ });
1615
+ }
1616
+ async getOpcodes(transactionIdOrHash) {
1617
+ return (await this.client.get(`/api/v1/contracts/results/${encodeURIComponent(transactionIdOrHash)}/opcodes`)).data;
1618
+ }
1619
+ };
1620
+ //#endregion
1621
+ //#region src/mappers/network.ts
1622
+ function mapTimestampRange(raw) {
1623
+ const r = asRecord(raw);
1624
+ return {
1625
+ from: strReq(r, "from"),
1626
+ to: str(r, "to")
1627
+ };
1628
+ }
1629
+ function mapServiceEndpoint(raw) {
1630
+ const r = asRecord(raw);
1631
+ return {
1632
+ ip_address_v4: strReq(r, "ip_address_v4"),
1633
+ port: num(r, "port"),
1634
+ domain_name: strReq(r, "domain_name")
1635
+ };
1636
+ }
1637
+ function mapNetworkNode(raw) {
1638
+ const r = asRecord(raw);
1639
+ return {
1640
+ admin_key: r.admin_key ?? null,
1641
+ description: strReq(r, "description"),
1642
+ file_id: strReq(r, "file_id"),
1643
+ max_stake: strReq(r, "max_stake"),
1644
+ memo: strReq(r, "memo"),
1645
+ min_stake: strReq(r, "min_stake"),
1646
+ node_account_id: strReq(r, "node_account_id"),
1647
+ node_id: num(r, "node_id"),
1648
+ node_cert_hash: strReq(r, "node_cert_hash"),
1649
+ public_key: strReq(r, "public_key"),
1650
+ reward_rate_start: strReq(r, "reward_rate_start"),
1651
+ service_endpoints: arr(r, "service_endpoints").map(mapServiceEndpoint),
1652
+ stake: strReq(r, "stake"),
1653
+ stake_not_rewarded: strReq(r, "stake_not_rewarded"),
1654
+ stake_rewarded: strReq(r, "stake_rewarded"),
1655
+ staking_period: mapTimestampRange(r.staking_period),
1656
+ timestamp: mapTimestampRange(r.timestamp)
1657
+ };
1658
+ }
1659
+ function mapNetworkStake(raw) {
1660
+ const r = asRecord(raw);
1661
+ return {
1662
+ max_stake_rewarded: strReq(r, "max_stake_rewarded"),
1663
+ max_staking_reward_rate_per_hbar: strReq(r, "max_staking_reward_rate_per_hbar"),
1664
+ max_total_reward: strReq(r, "max_total_reward"),
1665
+ node_reward_fee_fraction: num(r, "node_reward_fee_fraction"),
1666
+ reserved_staking_rewards: strReq(r, "reserved_staking_rewards"),
1667
+ reward_balance_threshold: strReq(r, "reward_balance_threshold"),
1668
+ stake_total: strReq(r, "stake_total"),
1669
+ staking_period: mapTimestampRange(r.staking_period),
1670
+ staking_period_duration: strReq(r, "staking_period_duration"),
1671
+ staking_periods_stored: strReq(r, "staking_periods_stored"),
1672
+ staking_reward_fee_fraction: num(r, "staking_reward_fee_fraction"),
1673
+ staking_reward_rate: strReq(r, "staking_reward_rate"),
1674
+ staking_start_threshold: strReq(r, "staking_start_threshold"),
1675
+ unreserved_staking_reward_balance: strReq(r, "unreserved_staking_reward_balance")
1676
+ };
1677
+ }
1678
+ function mapExchangeRate(raw) {
1679
+ const r = asRecord(raw);
1680
+ return {
1681
+ cent_equivalent: num(r, "cent_equivalent"),
1682
+ expiration_time: num(r, "expiration_time"),
1683
+ hbar_equivalent: num(r, "hbar_equivalent")
1684
+ };
1685
+ }
1686
+ function mapExchangeRateSet(raw) {
1687
+ const r = asRecord(raw);
1688
+ return {
1689
+ current_rate: mapExchangeRate(r.current_rate),
1690
+ next_rate: mapExchangeRate(r.next_rate),
1691
+ timestamp: strReq(r, "timestamp")
1692
+ };
1693
+ }
1694
+ function mapSupply(raw) {
1695
+ const r = asRecord(raw);
1696
+ return {
1697
+ released_supply: strReq(r, "released_supply"),
1698
+ timestamp: strReq(r, "timestamp"),
1699
+ total_supply: strReq(r, "total_supply")
1700
+ };
1701
+ }
1702
+ function mapFee(raw) {
1703
+ const r = asRecord(raw);
1704
+ return {
1705
+ gas: strReq(r, "gas"),
1706
+ transaction_type: strReq(r, "transaction_type")
1707
+ };
1708
+ }
1709
+ function mapFeeSchedule(raw) {
1710
+ const r = asRecord(raw);
1711
+ return {
1712
+ current: r.current != null ? arr(r, "current").map(mapFee) : void 0,
1713
+ next: r.next != null ? arr(r, "next").map(mapFee) : void 0,
1714
+ timestamp: strReq(r, "timestamp")
1715
+ };
1716
+ }
1717
+ //#endregion
1718
+ //#region src/resources/network.ts
1719
+ var NetworkResource = class {
1720
+ constructor(client) {
1721
+ this.client = client;
1722
+ }
1723
+ async getExchangeRate() {
1724
+ return mapExchangeRateSet((await this.client.get("/api/v1/network/exchangerate")).data);
1725
+ }
1726
+ async getFees(params) {
1727
+ return mapFeeSchedule((await this.client.get("/api/v1/network/fees", params)).data);
1728
+ }
1729
+ /**
1730
+ * POST /api/v1/network/fees — estimate fees from protobuf payload.
1731
+ *
1732
+ * EC52: Accepts raw Uint8Array (user must serialize protobuf externally).
1733
+ */
1734
+ async estimateFees(body) {
1735
+ return (await this.client.post("/api/v1/network/fees", body, { headers: { "Content-Type": "application/protobuf" } })).data;
1736
+ }
1737
+ getNodes(params) {
1738
+ return new Paginator({
1739
+ client: this.client,
1740
+ path: "/api/v1/network/nodes",
1741
+ params,
1742
+ extract: createPageExtractor("nodes", mapNetworkNode)
1743
+ });
1744
+ }
1745
+ async getStake() {
1746
+ return mapNetworkStake((await this.client.get("/api/v1/network/stake")).data);
1747
+ }
1748
+ async getSupply(params) {
1749
+ return mapSupply((await this.client.get("/api/v1/network/supply", params)).data);
1750
+ }
1751
+ };
1752
+ //#endregion
1753
+ //#region src/mappers/schedule.ts
1754
+ function mapKey(raw) {
1755
+ if (raw == null) return null;
1756
+ const r = asRecord(raw);
1757
+ return {
1758
+ _type: strReq(r, "_type"),
1759
+ key: strReq(r, "key")
1760
+ };
1761
+ }
1762
+ function mapSignature(raw) {
1763
+ const r = asRecord(raw);
1764
+ return {
1765
+ consensus_timestamp: strReq(r, "consensus_timestamp"),
1766
+ public_key_prefix: strReq(r, "public_key_prefix"),
1767
+ signature: strReq(r, "signature"),
1768
+ type: strReq(r, "type")
1769
+ };
1770
+ }
1771
+ /** EC58: Same type for list and detail. */
1772
+ function mapSchedule(raw) {
1773
+ const r = asRecord(raw);
1774
+ return {
1775
+ admin_key: mapKey(r.admin_key),
1776
+ consensus_timestamp: str(r, "consensus_timestamp"),
1777
+ creator_account_id: strReq(r, "creator_account_id"),
1778
+ deleted: bool(r, "deleted"),
1779
+ executed_timestamp: str(r, "executed_timestamp"),
1780
+ expiration_time: str(r, "expiration_time"),
1781
+ memo: strReq(r, "memo"),
1782
+ payer_account_id: strReq(r, "payer_account_id"),
1783
+ schedule_id: strReq(r, "schedule_id"),
1784
+ signatures: arr(r, "signatures").map(mapSignature),
1785
+ transaction_body: strReq(r, "transaction_body"),
1786
+ wait_for_expiry: bool(r, "wait_for_expiry")
1787
+ };
1788
+ }
1789
+ //#endregion
1790
+ //#region src/resources/schedules.ts
1791
+ var SchedulesResource = class {
1792
+ constructor(client) {
1793
+ this.client = client;
1794
+ }
1795
+ list(params) {
1796
+ return new Paginator({
1797
+ client: this.client,
1798
+ path: "/api/v1/schedules",
1799
+ params,
1800
+ extract: createPageExtractor("schedules", mapSchedule)
1801
+ });
1802
+ }
1803
+ async get(scheduleId) {
1804
+ return mapSchedule((await this.client.get(`/api/v1/schedules/${encodeURIComponent(scheduleId)}`)).data);
1805
+ }
1806
+ };
1807
+ //#endregion
1808
+ //#region src/mappers/transaction.ts
1809
+ function mapTransaction(raw) {
1810
+ const r = asRecord(raw);
1811
+ return {
1812
+ bytes: str(r, "bytes"),
1813
+ charged_tx_fee: strReq(r, "charged_tx_fee"),
1814
+ consensus_timestamp: strReq(r, "consensus_timestamp"),
1815
+ entity_id: str(r, "entity_id"),
1816
+ max_fee: strReq(r, "max_fee"),
1817
+ memo_base64: strReq(r, "memo_base64"),
1818
+ name: strReq(r, "name"),
1819
+ nft_transfers: arr(r, "nft_transfers").map((t) => {
1820
+ const tr = asRecord(t);
1821
+ return {
1822
+ is_approval: bool(tr, "is_approval"),
1823
+ receiver_account_id: str(tr, "receiver_account_id"),
1824
+ sender_account_id: str(tr, "sender_account_id"),
1825
+ serial_number: strReq(tr, "serial_number"),
1826
+ token_id: strReq(tr, "token_id")
1827
+ };
1828
+ }),
1829
+ node: str(r, "node"),
1830
+ nonce: num(r, "nonce"),
1831
+ parent_consensus_timestamp: str(r, "parent_consensus_timestamp"),
1832
+ result: strReq(r, "result"),
1833
+ scheduled: bool(r, "scheduled"),
1834
+ staking_reward_transfers: arr(r, "staking_reward_transfers").map((s) => {
1835
+ const sr = asRecord(s);
1836
+ return {
1837
+ account: strReq(sr, "account"),
1838
+ amount: strReq(sr, "amount")
1839
+ };
1840
+ }),
1841
+ token_transfers: arr(r, "token_transfers").map((t) => {
1842
+ const tr = asRecord(t);
1843
+ return {
1844
+ account: strReq(tr, "account"),
1845
+ amount: strReq(tr, "amount"),
1846
+ is_approval: bool(tr, "is_approval"),
1847
+ token_id: strReq(tr, "token_id")
1848
+ };
1849
+ }),
1850
+ transaction_hash: strReq(r, "transaction_hash"),
1851
+ transaction_id: strReq(r, "transaction_id"),
1852
+ transfers: arr(r, "transfers").map((t) => {
1853
+ const tr = asRecord(t);
1854
+ return {
1855
+ account: strReq(tr, "account"),
1856
+ amount: strReq(tr, "amount"),
1857
+ is_approval: bool(tr, "is_approval")
1858
+ };
1859
+ }),
1860
+ valid_duration_seconds: strReq(r, "valid_duration_seconds"),
1861
+ valid_start_timestamp: strReq(r, "valid_start_timestamp")
1862
+ };
1863
+ }
1864
+ /**
1865
+ * Unwraps the `{ transactions: [...] }` envelope returned by GET /transactions/{id}.
1866
+ *
1867
+ * EC21/150/151: Detail endpoint wraps in array. `.get()` extracts `transactions[0]`.
1868
+ */
1869
+ function unwrapTransaction(raw) {
1870
+ const txns = arr(asRecord(raw), "transactions");
1871
+ if (txns.length === 0) throw new Error("Transaction response contained empty transactions array");
1872
+ return mapTransaction(txns[0]);
1873
+ }
1874
+ /** EC77: NFT transaction has a separate shape. */
1875
+ function mapNftTransaction(raw) {
1876
+ const r = asRecord(raw);
1877
+ return {
1878
+ consensus_timestamp: strReq(r, "consensus_timestamp"),
1879
+ is_approval: bool(r, "is_approval"),
1880
+ nonce: num(r, "nonce"),
1881
+ receiver_account_id: str(r, "receiver_account_id"),
1882
+ sender_account_id: str(r, "sender_account_id"),
1883
+ transaction_id: strReq(r, "transaction_id"),
1884
+ type: strReq(r, "type")
1885
+ };
1886
+ }
1887
+ //#endregion
1888
+ //#region src/resources/tokens.ts
1889
+ var TokensResource = class {
1890
+ constructor(client) {
1891
+ this.client = client;
1892
+ }
1893
+ list(params) {
1894
+ return new Paginator({
1895
+ client: this.client,
1896
+ path: "/api/v1/tokens",
1897
+ params,
1898
+ extract: createPageExtractor("tokens", mapTokenSummary)
1899
+ });
1900
+ }
1901
+ async get(tokenId) {
1902
+ return mapTokenDetail((await this.client.get(`/api/v1/tokens/${encodeURIComponent(tokenId)}`)).data);
1903
+ }
1904
+ getBalances(tokenId, params) {
1905
+ return new Paginator({
1906
+ client: this.client,
1907
+ path: `/api/v1/tokens/${encodeURIComponent(tokenId)}/balances`,
1908
+ params,
1909
+ extract: createPageExtractor("balances", mapTokenBalanceEntry)
1910
+ });
1911
+ }
1912
+ getNFTs(tokenId, params) {
1913
+ return new Paginator({
1914
+ client: this.client,
1915
+ path: `/api/v1/tokens/${encodeURIComponent(tokenId)}/nfts`,
1916
+ params,
1917
+ extract: createPageExtractor("nfts", mapTokenNft)
1918
+ });
1919
+ }
1920
+ async getNFTBySerial(tokenId, serialNumber) {
1921
+ return mapTokenNft((await this.client.get(`/api/v1/tokens/${encodeURIComponent(tokenId)}/nfts/${serialNumber}`)).data);
1922
+ }
1923
+ getNFTTransactions(tokenId, serialNumber, params) {
1924
+ return new Paginator({
1925
+ client: this.client,
1926
+ path: `/api/v1/tokens/${encodeURIComponent(tokenId)}/nfts/${serialNumber}/transactions`,
1927
+ params,
1928
+ extract: createPageExtractor("transactions", mapNftTransaction)
1929
+ });
1930
+ }
1931
+ };
1932
+ //#endregion
1933
+ //#region src/mappers/topic.ts
1934
+ function mapChunkInfo(raw) {
1935
+ if (raw == null) return null;
1936
+ const r = asRecord(raw);
1937
+ const initTxRaw = asRecord(r.initial_transaction_id);
1938
+ return {
1939
+ initial_transaction_id: {
1940
+ account_id: strReq(initTxRaw, "account_id"),
1941
+ nonce: num(initTxRaw, "nonce"),
1942
+ scheduled: bool(initTxRaw, "scheduled"),
1943
+ transaction_valid_start: strReq(initTxRaw, "transaction_valid_start")
1944
+ },
1945
+ number: num(r, "number"),
1946
+ total: num(r, "total")
1947
+ };
1948
+ }
1949
+ /**
1950
+ * Maps a raw topic message to a `TopicMessage`.
1951
+ *
1952
+ * EC3/18: Auto-decodes Base64 `message` and `running_hash` to `Uint8Array`.
1953
+ */
1954
+ function mapTopicMessage(raw) {
1955
+ const r = asRecord(raw);
1956
+ const messageStr = strReq(r, "message");
1957
+ const runningHashStr = strReq(r, "running_hash");
1958
+ return {
1959
+ chunk_info: mapChunkInfo(r.chunk_info),
1960
+ consensus_timestamp: strReq(r, "consensus_timestamp"),
1961
+ message: decodeBase64(messageStr),
1962
+ payer_account_id: strReq(r, "payer_account_id"),
1963
+ running_hash: decodeBase64(runningHashStr),
1964
+ running_hash_version: num(r, "running_hash_version"),
1965
+ sequence_number: strReq(r, "sequence_number"),
1966
+ topic_id: strReq(r, "topic_id")
1967
+ };
1968
+ }
1969
+ //#endregion
1970
+ //#region src/pagination/stream.ts
1971
+ /**
1972
+ * Adaptive polling stream for topic messages.
1973
+ *
1974
+ * - Polls at high frequency (500ms) when messages are flowing.
1975
+ * - Backs off exponentially (up to 5s) when no messages arrive.
1976
+ * - Cancellable via `AbortController`.
1977
+ * - Rate-limit aware (shares the HttpClient's token bucket).
1978
+ */
1979
+ var TopicStream = class TopicStream {
1980
+ client;
1981
+ topicId;
1982
+ options;
1983
+ /** Minimum polling interval (ms). */
1984
+ static MIN_INTERVAL = 500;
1985
+ /** Maximum polling interval (ms). */
1986
+ static MAX_INTERVAL = 5e3;
1987
+ /** Factor by which interval increases on empty polls. */
1988
+ static BACKOFF_FACTOR = 1.5;
1989
+ constructor(client, topicId, options = {}) {
1990
+ this.client = client;
1991
+ this.topicId = topicId;
1992
+ this.options = {
1993
+ startTimestamp: options.startTimestamp ?? Math.floor(Date.now() / 1e3).toString(),
1994
+ interval: options.interval ?? TopicStream.MIN_INTERVAL,
1995
+ limit: options.limit ?? 100,
1996
+ signal: options.signal
1997
+ };
1998
+ }
1999
+ async *[Symbol.asyncIterator]() {
2000
+ let cursor = this.options.startTimestamp;
2001
+ let currentInterval = this.options.interval;
2002
+ while (!this.options.signal?.aborted) {
2003
+ try {
2004
+ const raw = (await this.client.get(`/api/v1/topics/${this.topicId}/messages`, {
2005
+ timestamp: `gt:${cursor}`,
2006
+ limit: String(this.options.limit),
2007
+ order: "asc"
2008
+ }, { signal: this.options.signal })).data;
2009
+ const messages = Array.isArray(raw.messages) ? raw.messages : [];
2010
+ if (messages.length > 0) {
2011
+ currentInterval = TopicStream.MIN_INTERVAL;
2012
+ for (const msg of messages) {
2013
+ const mapped = mapTopicMessage(msg);
2014
+ yield mapped;
2015
+ cursor = mapped.consensus_timestamp;
2016
+ }
2017
+ } else currentInterval = Math.min(currentInterval * TopicStream.BACKOFF_FACTOR, TopicStream.MAX_INTERVAL);
2018
+ } catch (_error) {
2019
+ if (this.options.signal?.aborted) return;
2020
+ currentInterval = Math.min(currentInterval * TopicStream.BACKOFF_FACTOR * 2, TopicStream.MAX_INTERVAL);
2021
+ }
2022
+ await this.sleep(currentInterval);
2023
+ }
2024
+ }
2025
+ sleep(ms) {
2026
+ return new Promise((resolve) => {
2027
+ const timer = setTimeout(resolve, ms);
2028
+ this.options.signal?.addEventListener("abort", () => {
2029
+ clearTimeout(timer);
2030
+ resolve();
2031
+ }, { once: true });
2032
+ });
2033
+ }
2034
+ };
2035
+ //#endregion
2036
+ //#region src/resources/topics.ts
2037
+ function mapTopicInfo(raw) {
2038
+ const r = asRecord(raw);
2039
+ const tsRaw = asRecord(r.timestamp);
2040
+ return {
2041
+ admin_key: r.admin_key ?? null,
2042
+ auto_renew_account: str(r, "auto_renew_account"),
2043
+ auto_renew_period: str(r, "auto_renew_period"),
2044
+ created_timestamp: strReq(r, "created_timestamp"),
2045
+ deleted: bool(r, "deleted"),
2046
+ memo: strReq(r, "memo"),
2047
+ submit_key: r.submit_key ?? null,
2048
+ timestamp: {
2049
+ from: strReq(tsRaw, "from"),
2050
+ to: str(tsRaw, "to")
2051
+ },
2052
+ topic_id: strReq(r, "topic_id")
2053
+ };
2054
+ }
2055
+ var TopicsResource = class {
2056
+ constructor(client) {
2057
+ this.client = client;
2058
+ }
2059
+ async get(topicId) {
2060
+ return mapTopicInfo((await this.client.get(`/api/v1/topics/${encodeURIComponent(topicId)}`)).data);
2061
+ }
2062
+ getMessages(topicId, params) {
2063
+ return new Paginator({
2064
+ client: this.client,
2065
+ path: `/api/v1/topics/${encodeURIComponent(topicId)}/messages`,
2066
+ params,
2067
+ extract: createPageExtractor("messages", mapTopicMessage)
2068
+ });
2069
+ }
2070
+ async getMessageBySequence(topicId, sequenceNumber) {
2071
+ return mapTopicMessage((await this.client.get(`/api/v1/topics/${encodeURIComponent(topicId)}/messages/${sequenceNumber}`)).data);
2072
+ }
2073
+ async getMessageByTimestamp(timestamp) {
2074
+ return mapTopicMessage((await this.client.get(`/api/v1/topics/messages/${encodeURIComponent(timestamp)}`)).data);
2075
+ }
2076
+ stream(topicId, options) {
2077
+ return new TopicStream(this.client, topicId, options);
2078
+ }
2079
+ };
2080
+ //#endregion
2081
+ //#region src/resources/transactions.ts
2082
+ var TransactionsResource = class {
2083
+ constructor(client) {
2084
+ this.client = client;
2085
+ }
2086
+ list(params) {
2087
+ return new Paginator({
2088
+ client: this.client,
2089
+ path: "/api/v1/transactions",
2090
+ params,
2091
+ extract: createPageExtractor("transactions", mapTransaction)
2092
+ });
2093
+ }
2094
+ /**
2095
+ * Get a transaction by ID.
2096
+ *
2097
+ * EC21/150/151: The API returns `{ transactions: [{ ... }] }`.
2098
+ * This method unwraps to return the single transaction.
2099
+ */
2100
+ async get(transactionId, params) {
2101
+ return unwrapTransaction((await this.client.get(`/api/v1/transactions/${encodeURIComponent(transactionId)}`, params)).data);
2102
+ }
2103
+ };
2104
+ //#endregion
2105
+ //#region src/client.ts
2106
+ /**
2107
+ * MirrorNodeClient — the main entrypoint for the Hiero Mirror Client SDK.
2108
+ *
2109
+ * ```ts
2110
+ * import { MirrorNodeClient } from '@satianurag/hiero-mirror-client';
2111
+ *
2112
+ * const client = new MirrorNodeClient({ network: 'testnet' });
2113
+ * const account = await client.accounts.get('0.0.1234');
2114
+ * ```
2115
+ *
2116
+ * @packageDocumentation
2117
+ */
2118
+ const NETWORK_URLS = {
2119
+ mainnet: "https://mainnet-public.mirrornode.hedera.com",
2120
+ testnet: "https://testnet.mirrornode.hedera.com",
2121
+ previewnet: "https://previewnet.mirrornode.hedera.com"
2122
+ };
2123
+ /**
2124
+ * The main Hiero Mirror Client SDK entry point.
2125
+ *
2126
+ * Provides typed access to all Mirror Node REST API resources with:
2127
+ * - Automatic pagination via `Paginator`
2128
+ * - Safe JSON parsing for int64 precision
2129
+ * - ETag caching, retry/backoff, and rate limiting
2130
+ * - Type-safe responses via response mappers
2131
+ */
2132
+ var MirrorNodeClient = class {
2133
+ /** Account operations: list, get, NFTs, tokens, rewards, allowances, airdrops. */
2134
+ accounts;
2135
+ /** Global balance queries. */
2136
+ balances;
2137
+ /** Block queries. */
2138
+ blocks;
2139
+ /** Contract queries and smart contract call simulation. */
2140
+ contracts;
2141
+ /** Network info: exchange rate, fees, nodes, stake, supply. */
2142
+ network;
2143
+ /** Schedule queries. */
2144
+ schedules;
2145
+ /** Token queries: list, get, balances, NFTs, NFT transactions. */
2146
+ tokens;
2147
+ /** Topic queries and HCS message streaming. */
2148
+ topics;
2149
+ /** Transaction queries. */
2150
+ transactions;
2151
+ /** The underlying HTTP client (exposed for advanced usage). */
2152
+ httpClient;
2153
+ constructor(options = {}) {
2154
+ let baseUrl;
2155
+ if (options.baseUrl) baseUrl = options.baseUrl;
2156
+ else baseUrl = NETWORK_URLS[options.network ?? "mainnet"];
2157
+ this.httpClient = new HttpClient({
2158
+ baseUrl,
2159
+ timeout: options.timeout ?? 3e4,
2160
+ retry: { maxRetries: options.maxRetries ?? 2 },
2161
+ rateLimitRps: options.rateLimitRps ?? 50,
2162
+ logger: options.logger,
2163
+ fetch: options.fetch
2164
+ });
2165
+ this.accounts = new AccountsResource(this.httpClient);
2166
+ this.balances = new BalancesResource(this.httpClient);
2167
+ this.blocks = new BlocksResource(this.httpClient);
2168
+ this.contracts = new ContractsResource(this.httpClient);
2169
+ this.network = new NetworkResource(this.httpClient);
2170
+ this.schedules = new SchedulesResource(this.httpClient);
2171
+ this.tokens = new TokensResource(this.httpClient);
2172
+ this.topics = new TopicsResource(this.httpClient);
2173
+ this.transactions = new TransactionsResource(this.httpClient);
2174
+ }
2175
+ };
2176
+ //#endregion
2177
+ //#region src/errors/HieroCapabilityError.ts
2178
+ /**
2179
+ * Thrown when a known feature is disabled on the mirror node
2180
+ * (e.g., `/stateproof` returns 404 on valid transaction IDs — EC62).
2181
+ *
2182
+ * Distinct from {@link HieroNotFoundError} because the entity exists
2183
+ * but the feature is unavailable on this mirror node instance.
2184
+ *
2185
+ * @public
2186
+ */
2187
+ var HieroCapabilityError = class extends HieroError {
2188
+ /** The feature/endpoint that is disabled. */
2189
+ feature;
2190
+ constructor(message, options) {
2191
+ super(message, {
2192
+ statusCode: 404,
2193
+ rawBody: options.rawBody
2194
+ });
2195
+ this.name = "HieroCapabilityError";
2196
+ this.feature = options.feature;
2197
+ }
2198
+ toJSON() {
2199
+ return {
2200
+ ...super.toJSON(),
2201
+ feature: this.feature
2202
+ };
2203
+ }
2204
+ };
2205
+ //#endregion
2206
+ //#region src/index.ts
2207
+ /**
2208
+ * @satianurag/hiero-mirror-client
2209
+ *
2210
+ * Standalone TypeScript client for the Hedera/Hiero Mirror Node REST API.
2211
+ *
2212
+ * @packageDocumentation
2213
+ */
2214
+ const VERSION = "0.0.0";
2215
+ //#endregion
2216
+ export { HieroCapabilityError, HieroError, HieroNetworkError, HieroNotFoundError, HieroParseError, HieroRateLimitError, HieroServerError, HieroTimeoutError, HieroValidationError, MirrorNodeClient, Paginator, TopicStream, VERSION };
2217
+
2218
+ //# sourceMappingURL=index.mjs.map