@ohmpathorn/block-node-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/README.md ADDED
@@ -0,0 +1,322 @@
1
+ # @ohmpathorn/block-node-client
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@ohmpathorn/block-node-client)](https://www.npmjs.com/package/@ohmpathorn/block-node-client)
4
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D16-brightgreen)](https://nodejs.org)
6
+
7
+ JavaScript/TypeScript SDK for the [Hedera Block Node](https://github.com/hiero-ledger/hiero-block-node) gRPC API.
8
+
9
+ Wraps all four Block Node RPCs with a clean async/callback interface, full TypeScript types, and built-in protobuf decoding — so you never have to touch raw bytes.
10
+
11
+ ---
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @ohmpathorn/block-node-client
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ ```ts
22
+ import { BlockNodeClient } from "@ohmpathorn/block-node-client";
23
+
24
+ const client = new BlockNodeClient({
25
+ endpoint: "s01.test.blk.ams.lat.ope.eng.hashgraph.io:40840",
26
+ });
27
+
28
+ // Check node health
29
+ const status = await client.serverStatus();
30
+ console.log("Latest block:", status.lastAvailableBlock);
31
+
32
+ // Subscribe to live blocks
33
+ const handle = client.subscribeBlockStream(
34
+ { startBlockNumber: status.lastAvailableBlock - 10n },
35
+ {
36
+ onStatus: (code, name) => console.log("Stream:", name),
37
+ onBlock: (num, items) => console.log(`Block ${num} — ${items.length} items`),
38
+ onError: (err) => console.error(err.message),
39
+ onEnd: () => client.close(),
40
+ },
41
+ );
42
+
43
+ // Cancel after 60 seconds
44
+ setTimeout(() => handle.cancel(), 60_000);
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Testnet endpoints
50
+
51
+ All endpoints use port **40840**.
52
+
53
+ | Region | Endpoint |
54
+ |--------|----------|
55
+ | Amsterdam | `s01.test.blk.ams.lat.ope.eng.hashgraph.io:40840` |
56
+ | Singapore | `s01.test.blk.sgp.lat.ope.eng.hashgraph.io:40840` |
57
+ | Chicago | `s01.test.blk.chi.lat.ope.eng.hashgraph.io:40840` |
58
+ | Tier 2 | `lfh01.testnet.blocknode.hashgraph-devops.com:40840` |
59
+
60
+ ---
61
+
62
+ ## API reference
63
+
64
+ ### `new BlockNodeClient(options)`
65
+
66
+ | Option | Type | Default | Description |
67
+ |--------|------|---------|-------------|
68
+ | `endpoint` | `string` | required | `host:port` of the block node |
69
+ | `tls` | `"tls" \| "insecure"` | `"tls"` | TLS or plaintext |
70
+ | `timeout` | `number` | `10000` | Unary call timeout in ms |
71
+
72
+ ---
73
+
74
+ ### `client.serverStatus()` → `Promise<ServerStatusResponse>`
75
+
76
+ Lightweight health check. Returns the available block range and node state. Always call this first to validate the range before subscribing.
77
+
78
+ ```ts
79
+ const status = await client.serverStatus();
80
+ console.log(status.firstAvailableBlock); // bigint
81
+ console.log(status.lastAvailableBlock); // bigint
82
+ console.log(status.onlyLatestState); // boolean
83
+ ```
84
+
85
+ ---
86
+
87
+ ### `client.serverStatusDetail()` → `Promise<ServerStatusDetailResponse>`
88
+
89
+ Full capability report: software versions, all available block ranges, and the network address book.
90
+
91
+ ```ts
92
+ const detail = await client.serverStatusDetail();
93
+
94
+ const v = detail.versionInformation?.blockNodeVersion;
95
+ console.log(`Block node: ${v?.major}.${v?.minor}.${v?.patch}`);
96
+
97
+ detail.availableRanges.forEach(r =>
98
+ console.log(`${r.rangeStart} → ${r.rangeEnd}`)
99
+ );
100
+ ```
101
+
102
+ ---
103
+
104
+ ### `client.getBlock(request)` → `Promise<GetBlockResponse>`
105
+
106
+ Fetch a single block by number or the latest block.
107
+
108
+ ```ts
109
+ // By number
110
+ const result = await client.getBlock({ blockNumber: 38764703n });
111
+
112
+ // Latest
113
+ const latest = await client.getBlock({ retrieveLatest: true });
114
+
115
+ console.log(result.statusName); // "SUCCESS"
116
+ console.log(result.block?.length); // number of BlockItems
117
+ ```
118
+
119
+ ---
120
+
121
+ ### `client.subscribeBlockStream(options, callbacks)` → `StreamHandle`
122
+
123
+ Server-streaming RPC. Buffers `BlockItemSet` messages and fires `onBlock` once per complete block (after `end_of_block` is received).
124
+
125
+ ```ts
126
+ const handle = client.subscribeBlockStream(
127
+ {
128
+ startBlockNumber: 38764000n,
129
+ endBlockNumber: 0n, // 0 = live open-ended stream
130
+ },
131
+ {
132
+ onStatus: (code, name) => console.log("Stream status:", name),
133
+ onBlock: (blockNumber, items) => {
134
+ items.filter(i => i.kind === "event_transaction").forEach(item => {
135
+ console.log(item.payload.type); // "CRYPTO_TRANSFER"
136
+ console.log(item.payload.transactionId?.toString()); // "0.0.3569@..."
137
+ });
138
+ },
139
+ onError: (err) => console.error(err.message),
140
+ onEnd: () => console.log("Stream ended"),
141
+ },
142
+ );
143
+
144
+ handle.cancel(); // stop at any time
145
+ ```
146
+
147
+ > **Note:** `endBlockNumber: 0` is automatically converted to `uint64_max` for a live stream. Pass an explicit number for a bounded historical range.
148
+
149
+ ---
150
+
151
+ ### `client.close()`
152
+
153
+ Releases gRPC connections and cleans up temp files. Always call when done.
154
+
155
+ ---
156
+
157
+ ## Block items
158
+
159
+ Each block is an ordered list of `BlockItem` objects. Every item has:
160
+
161
+ | Property | Type | Description |
162
+ |----------|------|-------------|
163
+ | `kind` | `BlockItemKind` | Which oneof field is set |
164
+ | `payload` | typed per `kind` | Decoded message |
165
+ | `raw` | `Buffer` | Original bytes |
166
+
167
+ ### Item kinds
168
+
169
+ | Kind | Description | Payload type |
170
+ |------|-------------|--------------|
171
+ | `block_header` | Block number, hash algorithm, SW versions | `BlockHeader` |
172
+ | `event_header` | Gossip event metadata | `EventHeader` |
173
+ | `round_header` | Consensus round number | `RoundHeader` |
174
+ | `event_transaction` | Transaction type, ID, memo, raw bytes | `EventTransaction` |
175
+ | `transaction_result` | Status, fee, payer, transfers, tx hash | `TransactionResult` |
176
+ | `state_changes` | Which state tables changed | `StateChanges` |
177
+ | `filtered_item_hash` | Hash placeholder for filtered items | `FilteredItemHash` |
178
+ | `block_proof` | TSS block signature + Merkle proof | `BlockProof` |
179
+ | `record_file` | Historical block (pre-HIP-1056) with embedded transactions | `RecordFile` |
180
+ | `address_book_proof` | Network address book hashes for record-file blocks | `AddressBookProof` |
181
+
182
+ ### Example: switch on kind
183
+
184
+ ```ts
185
+ items.forEach(item => {
186
+ switch (item.kind) {
187
+ case "block_header":
188
+ console.log("Block #", item.payload.number);
189
+ break;
190
+
191
+ case "event_transaction":
192
+ console.log(item.payload.type); // "CRYPTO_TRANSFER"
193
+ console.log(item.payload.transactionId?.toString()); // "0.0.3@1753912345.000000001"
194
+ console.log(item.payload.memo);
195
+ break;
196
+
197
+ case "transaction_result":
198
+ console.log(item.payload.statusName); // "SUCCESS"
199
+ console.log(item.payload.transactionFee); // bigint tinybars
200
+ item.payload.transfers.forEach(t =>
201
+ console.log(` ${t.accountId?.accountNum}: ${t.amount}`)
202
+ );
203
+ break;
204
+
205
+ case "record_file":
206
+ // Historical block format — transactions are nested inside
207
+ item.payload.transactions.forEach(tx =>
208
+ console.log(tx.type, tx.transactionId?.toString())
209
+ );
210
+ break;
211
+ }
212
+ });
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Transaction IDs
218
+
219
+ Hedera transaction IDs follow the format `shard.realm.account@seconds.nanos`:
220
+
221
+ ```ts
222
+ const txId = item.payload.transactionId;
223
+ console.log(txId.toString());
224
+ // "0.0.3569@1753912345.123456789"
225
+
226
+ console.log(txId.accountId);
227
+ // { shardNum: 0n, realmNum: 0n, accountNum: 3569n }
228
+
229
+ console.log(txId.transactionValidStart.toDate());
230
+ // JavaScript Date
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Transaction types
236
+
237
+ All 55 Hedera transaction types are decoded automatically. You can also import the lookup table directly:
238
+
239
+ ```ts
240
+ import { TX_TYPES } from "@ohmpathorn/block-node-client";
241
+
242
+ TX_TYPES[14] // "CRYPTO_TRANSFER"
243
+ TX_TYPES[27] // "CONSENSUS_SUBMIT_MESSAGE"
244
+ TX_TYPES[50] // "ETHEREUM_TRANSACTION"
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Status codes
250
+
251
+ ```ts
252
+ import { SubscribeStreamCode, BlockResponseCode } from "@ohmpathorn/block-node-client";
253
+
254
+ // Stream subscription status (first message)
255
+ SubscribeStreamCode.SUCCESS // 1
256
+ SubscribeStreamCode.INVALID_START_BLOCK_NUMBER // 4
257
+ SubscribeStreamCode.INVALID_END_BLOCK_NUMBER // 5 — caused by sending end_block = 0
258
+ SubscribeStreamCode.NOT_AVAILABLE // 6 — block not on this node
259
+
260
+ // getBlock response status
261
+ BlockResponseCode.SUCCESS // 1
262
+ BlockResponseCode.NOT_FOUND // 4
263
+ BlockResponseCode.NOT_AVAILABLE // 5
264
+ ```
265
+
266
+ ---
267
+
268
+ ## TypeScript
269
+
270
+ Full types ship with the package. Import what you need:
271
+
272
+ ```ts
273
+ import {
274
+ BlockNodeClient,
275
+ BlockNodeClientOptions,
276
+ ServerStatusResponse,
277
+ ServerStatusDetailResponse,
278
+ BlockItem,
279
+ BlockItemKind,
280
+ BlockHeader,
281
+ EventTransaction,
282
+ TransactionResult,
283
+ RecordFile,
284
+ AddressBookProof,
285
+ StreamHandle,
286
+ SubscribeStreamCode,
287
+ BlockResponseCode,
288
+ } from "@ohmpathorn/block-node-client";
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Examples
294
+
295
+ ```bash
296
+ # Basic usage — all four APIs
297
+ node examples/basic.js
298
+
299
+ # Live transaction monitor
300
+ node examples/transaction-monitor.js
301
+
302
+ # Multi-endpoint failover
303
+ node examples/multi-endpoint.js
304
+
305
+ # Plaintext instead of TLS
306
+ USE_INSECURE=1 node examples/basic.js
307
+
308
+ # Different endpoint
309
+ ENDPOINT=s01.test.blk.sgp.lat.ope.eng.hashgraph.io:40840 node examples/basic.js
310
+ ```
311
+
312
+ ---
313
+
314
+ ## How it works
315
+
316
+ The SDK writes the necessary protobuf definitions to a temp directory at startup and loads them via `@grpc/proto-loader`. Inner `BlockItem` payloads are decoded manually using a hand-rolled `ProtoReader` rather than generated stubs — this makes the SDK resilient to schema evolution and unknown fields.
317
+
318
+ ---
319
+
320
+ ## License
321
+
322
+ [Apache 2.0](LICENSE)
@@ -0,0 +1,81 @@
1
+ import { BlockNodeClientOptions, ServerStatusResponse, ServerStatusDetailResponse, BlockRequestSpecifier, GetBlockResponse, BlockStreamOptions, BlockStreamCallbacks, StreamHandle } from "./types";
2
+ /**
3
+ * BlockNodeClient — the main entry point for the Hedera Block Node SDK.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { BlockNodeClient } from "@ohmpathorn/block-node-client";
8
+ *
9
+ * const client = new BlockNodeClient({
10
+ * endpoint: "s01.test.blk.ams.lat.ope.eng.hashgraph.io:40840",
11
+ * });
12
+ *
13
+ * const status = await client.serverStatus();
14
+ * console.log(status.lastAvailableBlock);
15
+ *
16
+ * client.close();
17
+ * ```
18
+ */
19
+ export declare class BlockNodeClient {
20
+ private readonly options;
21
+ private proto;
22
+ private timeout;
23
+ constructor(options: BlockNodeClientOptions);
24
+ /**
25
+ * Lightweight health check.
26
+ * Returns the first/last available block and whether the node is running.
27
+ * Always call this before subscribing to validate the block range.
28
+ */
29
+ serverStatus(): Promise<ServerStatusResponse>;
30
+ /**
31
+ * Full capability report: versions, available block ranges, TSS data,
32
+ * and the current network address book.
33
+ */
34
+ serverStatusDetail(): Promise<ServerStatusDetailResponse>;
35
+ /**
36
+ * Fetch a single block by number or retrieve the latest block.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const result = await client.getBlock({ blockNumber: 38764703n });
41
+ * const latest = await client.getBlock({ retrieveLatest: true });
42
+ * ```
43
+ */
44
+ getBlock(request: BlockRequestSpecifier): Promise<GetBlockResponse>;
45
+ /**
46
+ * Subscribe to a live or historical block stream.
47
+ *
48
+ * The server sends `block_items` batches followed by `end_of_block` for each
49
+ * block. This method accumulates items per block and calls `onBlock` once the
50
+ * block is complete.
51
+ *
52
+ * Passing `endBlockNumber: 0` (or omitting it) subscribes to a live stream.
53
+ *
54
+ * @returns A `StreamHandle` with a `cancel()` method.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const status = await client.serverStatus();
59
+ *
60
+ * const handle = client.subscribeBlockStream(
61
+ * { startBlockNumber: status.lastAvailableBlock - 10n },
62
+ * {
63
+ * onStatus: (code, name) => console.log("Stream status:", name),
64
+ * onBlock: (num, items) => console.log(`Block ${num}: ${items.length} items`),
65
+ * onError: (err) => console.error("Stream error:", err.message),
66
+ * onEnd: () => console.log("Stream ended"),
67
+ * },
68
+ * );
69
+ *
70
+ * // Cancel after 30 seconds
71
+ * setTimeout(() => handle.cancel(), 30_000);
72
+ * ```
73
+ */
74
+ subscribeBlockStream(options: BlockStreamOptions, callbacks: BlockStreamCallbacks): StreamHandle;
75
+ /**
76
+ * Release gRPC resources and clean up temp proto files.
77
+ * Call when you're done with the client.
78
+ */
79
+ close(): void;
80
+ }
81
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,EAEhB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EAMb,MAAM,SAAS,CAAC;AAcjB;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAS;gBAEK,OAAO,EAAE,sBAAsB;IAU5D;;;;OAIG;IACH,YAAY,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAqB7C;;;OAGG;IACH,kBAAkB,IAAI,OAAO,CAAC,0BAA0B,CAAC;IA2CzD;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqCnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,oBAAoB,CAClB,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,oBAAoB,GAC9B,YAAY;IA0Df;;;OAGG;IACH,KAAK,IAAI,IAAI;CAGd"}
package/dist/client.js ADDED
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BlockNodeClient = void 0;
37
+ const grpc = __importStar(require("@grpc/grpc-js"));
38
+ const proto_manager_1 = require("./proto-manager");
39
+ const decoder_1 = require("./decoder");
40
+ const types_1 = require("./types");
41
+ const UINT64_MAX = "18446744073709551615";
42
+ const SUBSCRIBE_CODE_NAMES = {
43
+ 0: "UNKNOWN", 1: "SUCCESS", 2: "INVALID_REQUEST", 3: "ERROR",
44
+ 4: "INVALID_START_BLOCK_NUMBER", 5: "INVALID_END_BLOCK_NUMBER", 6: "NOT_AVAILABLE",
45
+ };
46
+ const BLOCK_CODE_NAMES = {
47
+ "0": "UNKNOWN", "1": "SUCCESS", "2": "INVALID_REQUEST",
48
+ "3": "ERROR", "4": "NOT_FOUND", "5": "NOT_AVAILABLE",
49
+ };
50
+ /**
51
+ * BlockNodeClient — the main entry point for the Hedera Block Node SDK.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { BlockNodeClient } from "@ohmpathorn/block-node-client";
56
+ *
57
+ * const client = new BlockNodeClient({
58
+ * endpoint: "s01.test.blk.ams.lat.ope.eng.hashgraph.io:40840",
59
+ * });
60
+ *
61
+ * const status = await client.serverStatus();
62
+ * console.log(status.lastAvailableBlock);
63
+ *
64
+ * client.close();
65
+ * ```
66
+ */
67
+ class BlockNodeClient {
68
+ constructor(options) {
69
+ this.options = options;
70
+ this.proto = new proto_manager_1.ProtoManager(options.endpoint, options.tls ?? "tls");
71
+ this.timeout = options.timeout ?? 10000;
72
+ }
73
+ // ── 1. serverStatus ──────────────────────────────────────────────────────────
74
+ /**
75
+ * Lightweight health check.
76
+ * Returns the first/last available block and whether the node is running.
77
+ * Always call this before subscribing to validate the block range.
78
+ */
79
+ serverStatus() {
80
+ return new Promise((resolve, reject) => {
81
+ const deadline = new Date(Date.now() + this.timeout);
82
+ this.proto.nodeClient.serverStatus({}, { deadline }, (err, res) => {
83
+ if (err)
84
+ return reject(err);
85
+ resolve({
86
+ firstAvailableBlock: BigInt(res.first_available_block ?? "0"),
87
+ lastAvailableBlock: BigInt(res.last_available_block ?? "0"),
88
+ nextExpectedBlock: BigInt(res.next_expected_block ?? "0"),
89
+ onlyLatestState: Boolean(res.only_latest_state),
90
+ });
91
+ });
92
+ });
93
+ }
94
+ // ── 2. serverStatusDetail ────────────────────────────────────────────────────
95
+ /**
96
+ * Full capability report: versions, available block ranges, TSS data,
97
+ * and the current network address book.
98
+ */
99
+ serverStatusDetail() {
100
+ return new Promise((resolve, reject) => {
101
+ const deadline = new Date(Date.now() + this.timeout);
102
+ this.proto.nodeClient.serverStatusDetail({}, { deadline }, (err, res) => {
103
+ if (err)
104
+ return reject(err);
105
+ const mapRange = (r) => ({
106
+ rangeStart: BigInt(r.range_start ?? "0"),
107
+ rangeEnd: BigInt(r.range_end ?? "0"),
108
+ });
109
+ const mapSemver = (v) => v ? { major: Number(v.major), minor: Number(v.minor), patch: Number(v.patch) } : undefined;
110
+ let versionInformation;
111
+ if (res.version_information) {
112
+ const vi = res.version_information;
113
+ versionInformation = {
114
+ streamProtoVersion: mapSemver(vi.stream_proto_version),
115
+ blockNodeVersion: mapSemver(vi.block_node_version),
116
+ installedPluginVersions: (vi.installed_plugin_versions ?? []).map((p) => ({
117
+ pluginId: p.plugin_id ?? "",
118
+ pluginSoftwareVersion: mapSemver(p.plugin_software_version),
119
+ pluginFeatureNames: p.plugin_feature_names ?? [],
120
+ })),
121
+ };
122
+ }
123
+ resolve({
124
+ versionInformation,
125
+ availableRanges: (res.available_ranges ?? []).map(mapRange),
126
+ storedRanges: (res.stored_ranges ?? []).map(mapRange),
127
+ });
128
+ });
129
+ });
130
+ }
131
+ // ── 3. getBlock ──────────────────────────────────────────────────────────────
132
+ /**
133
+ * Fetch a single block by number or retrieve the latest block.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const result = await client.getBlock({ blockNumber: 38764703n });
138
+ * const latest = await client.getBlock({ retrieveLatest: true });
139
+ * ```
140
+ */
141
+ getBlock(request) {
142
+ return new Promise((resolve, reject) => {
143
+ const deadline = new Date(Date.now() + this.timeout);
144
+ const grpcRequest = "blockNumber" in request
145
+ ? { block_number: request.blockNumber.toString() }
146
+ : { retrieve_latest: true };
147
+ this.proto.blockClient.getBlock(grpcRequest, { deadline }, (err, res) => {
148
+ if (err)
149
+ return reject(err);
150
+ // Status may come back as enum string or number
151
+ const statusRaw = res.status ?? "0";
152
+ const statusNum = typeof statusRaw === "string" && isNaN(Number(statusRaw))
153
+ ? Object.values(types_1.BlockResponseCode).indexOf(statusRaw)
154
+ : Number(statusRaw);
155
+ const statusName = BLOCK_CODE_NAMES[String(statusNum)] ?? `CODE_${statusNum}`;
156
+ let block;
157
+ if (res.block && res.block.items) {
158
+ block = res.block.items.map(raw => (0, decoder_1.decodeBlockItem)(Buffer.from(raw)));
159
+ }
160
+ resolve({ status: statusNum, statusName, block });
161
+ });
162
+ });
163
+ }
164
+ // ── 4. subscribeBlockStream ──────────────────────────────────────────────────
165
+ /**
166
+ * Subscribe to a live or historical block stream.
167
+ *
168
+ * The server sends `block_items` batches followed by `end_of_block` for each
169
+ * block. This method accumulates items per block and calls `onBlock` once the
170
+ * block is complete.
171
+ *
172
+ * Passing `endBlockNumber: 0` (or omitting it) subscribes to a live stream.
173
+ *
174
+ * @returns A `StreamHandle` with a `cancel()` method.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * const status = await client.serverStatus();
179
+ *
180
+ * const handle = client.subscribeBlockStream(
181
+ * { startBlockNumber: status.lastAvailableBlock - 10n },
182
+ * {
183
+ * onStatus: (code, name) => console.log("Stream status:", name),
184
+ * onBlock: (num, items) => console.log(`Block ${num}: ${items.length} items`),
185
+ * onError: (err) => console.error("Stream error:", err.message),
186
+ * onEnd: () => console.log("Stream ended"),
187
+ * },
188
+ * );
189
+ *
190
+ * // Cancel after 30 seconds
191
+ * setTimeout(() => handle.cancel(), 30_000);
192
+ * ```
193
+ */
194
+ subscribeBlockStream(options, callbacks) {
195
+ const start = BigInt(options.startBlockNumber);
196
+ const endRaw = options.endBlockNumber ?? 0;
197
+ // Sending 0 causes INVALID_END_BLOCK_NUMBER — convert to uint64_max for live streams
198
+ const end = BigInt(endRaw) === 0n ? UINT64_MAX : BigInt(endRaw).toString();
199
+ const stream = this.proto.streamClient.subscribeBlockStream({
200
+ start_block_number: start.toString(),
201
+ end_block_number: end,
202
+ });
203
+ let pending = [];
204
+ stream.on("data", (response) => {
205
+ // ── Status (first message) ─────────────────────────────────────────────
206
+ if (response.response === "status") {
207
+ const code = parseInt(response.status, 10);
208
+ const name = SUBSCRIBE_CODE_NAMES[code] ?? `CODE_${code}`;
209
+ callbacks.onStatus?.(code, name);
210
+ return;
211
+ }
212
+ // ── BlockItemSet (one or more items per message) ───────────────────────
213
+ if (response.response === "block_items") {
214
+ const rawItems = response.block_items?.block_items ?? [];
215
+ rawItems.forEach(raw => pending.push(Buffer.from(raw)));
216
+ return;
217
+ }
218
+ // ── end_of_block (block complete) ──────────────────────────────────────
219
+ if (response.response === "end_of_block") {
220
+ const blockNumber = BigInt(response.end_of_block?.block_number ?? "0");
221
+ const items = pending.map(raw => (0, decoder_1.decodeBlockItem)(raw));
222
+ pending = [];
223
+ callbacks.onBlock?.(blockNumber, items);
224
+ return;
225
+ }
226
+ });
227
+ stream.on("error", (err) => {
228
+ if (err.code === grpc.status.CANCELLED) {
229
+ callbacks.onEnd?.();
230
+ return;
231
+ }
232
+ callbacks.onError?.(err);
233
+ });
234
+ stream.on("end", () => {
235
+ callbacks.onEnd?.();
236
+ });
237
+ return {
238
+ cancel: () => stream.cancel(),
239
+ };
240
+ }
241
+ // ── Lifecycle ────────────────────────────────────────────────────────────────
242
+ /**
243
+ * Release gRPC resources and clean up temp proto files.
244
+ * Call when you're done with the client.
245
+ */
246
+ close() {
247
+ this.proto.cleanup();
248
+ }
249
+ }
250
+ exports.BlockNodeClient = BlockNodeClient;
251
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAsC;AACtC,mDAA+C;AAC/C,uCAA4C;AAC5C,mCAeiB;AAEjB,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C,MAAM,oBAAoB,GAA2B;IACnD,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,OAAO;IAC5D,CAAC,EAAE,4BAA4B,EAAE,CAAC,EAAE,0BAA0B,EAAE,CAAC,EAAE,eAAe;CACnF,CAAC;AAEF,MAAM,gBAAgB,GAA2B;IAC/C,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,iBAAiB;IACtD,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe;CACrD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,eAAe;IAI1B,YAA6B,OAA+B;QAA/B,YAAO,GAAP,OAAO,CAAwB;QAC1D,IAAI,CAAC,KAAK,GAAG,IAAI,4BAAY,CAC3B,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,GAAG,IAAI,KAAK,CACrB,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAM,CAAC;IAC3C,CAAC;IAED,gFAAgF;IAEhF;;;;OAIG;IACH,YAAY;QACV,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YACpD,IAAI,CAAC,KAAK,CAAC,UAAkB,CAAC,YAAY,CACzC,EAAE,EACF,EAAE,QAAQ,EAAE,EACZ,CAAC,GAA6B,EAAE,GAAQ,EAAE,EAAE;gBAC1C,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,OAAO,CAAC;oBACN,mBAAmB,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,IAAI,GAAG,CAAC;oBAC7D,kBAAkB,EAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,IAAK,GAAG,CAAC;oBAC7D,iBAAiB,EAAI,MAAM,CAAC,GAAG,CAAC,mBAAmB,IAAM,GAAG,CAAC;oBAC7D,eAAe,EAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;iBACpD,CAAC,CAAC;YACL,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gFAAgF;IAEhF;;;OAGG;IACH,kBAAkB;QAChB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YACpD,IAAI,CAAC,KAAK,CAAC,UAAkB,CAAC,kBAAkB,CAC/C,EAAE,EACF,EAAE,QAAQ,EAAE,EACZ,CAAC,GAA6B,EAAE,GAAQ,EAAE,EAAE;gBAC1C,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAE5B,MAAM,QAAQ,GAAG,CAAC,CAAM,EAAc,EAAE,CAAC,CAAC;oBACxC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI,GAAG,CAAC;oBACxC,QAAQ,EAAI,MAAM,CAAC,CAAC,CAAC,SAAS,IAAM,GAAG,CAAC;iBACzC,CAAC,CAAC;gBAEH,MAAM,SAAS,GAAG,CAAC,CAAM,EAA+B,EAAE,CACxD,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;gBAE7F,IAAI,kBAAiD,CAAC;gBACtD,IAAI,GAAG,CAAC,mBAAmB,EAAE,CAAC;oBAC5B,MAAM,EAAE,GAAG,GAAG,CAAC,mBAAmB,CAAC;oBACnC,kBAAkB,GAAG;wBACnB,kBAAkB,EAAE,SAAS,CAAC,EAAE,CAAC,oBAAoB,CAAC;wBACtD,gBAAgB,EAAI,SAAS,CAAC,EAAE,CAAC,kBAAkB,CAAC;wBACpD,uBAAuB,EAAE,CAAC,EAAE,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;4BAC7E,QAAQ,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;4BAC3B,qBAAqB,EAAE,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC;4BAC3D,kBAAkB,EAAE,CAAC,CAAC,oBAAoB,IAAI,EAAE;yBACjD,CAAC,CAAC;qBACJ,CAAC;gBACJ,CAAC;gBAED,OAAO,CAAC;oBACN,kBAAkB;oBAClB,eAAe,EAAE,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;oBAC3D,YAAY,EAAK,CAAC,GAAG,CAAC,aAAa,IAAO,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;iBAC5D,CAAC,CAAC;YACL,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gFAAgF;IAEhF;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAA8B;QACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YACrD,MAAM,WAAW,GACf,aAAa,IAAI,OAAO;gBACtB,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE;gBAClD,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;YAE/B,IAAI,CAAC,KAAK,CAAC,WAAmB,CAAC,QAAQ,CACtC,WAAW,EACX,EAAE,QAAQ,EAAE,EACZ,CAAC,GAA6B,EAAE,GAAQ,EAAE,EAAE;gBAC1C,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAE5B,gDAAgD;gBAChD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;gBACpC,MAAM,SAAS,GACb,OAAO,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;oBACvD,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,yBAAiB,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;oBACrD,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACxB,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,QAAQ,SAAS,EAAE,CAAC;gBAE9E,IAAI,KAA8B,CAAC;gBACnC,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACjC,KAAK,GAAI,GAAG,CAAC,KAAK,CAAC,KAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAC9C,IAAA,yBAAe,EAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAClC,CAAC;gBACJ,CAAC;gBAED,OAAO,CAAC,EAAE,MAAM,EAAE,SAA8B,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;YACzE,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gFAAgF;IAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,oBAAoB,CAClB,OAA2B,EAC3B,SAA+B;QAE/B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC;QAC3C,qFAAqF;QACrF,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QAE3E,MAAM,MAAM,GAAI,IAAI,CAAC,KAAK,CAAC,YAAoB,CAAC,oBAAoB,CAAC;YACnE,kBAAkB,EAAE,KAAK,CAAC,QAAQ,EAAE;YACpC,gBAAgB,EAAI,GAAG;SACxB,CAAC,CAAC;QAEH,IAAI,OAAO,GAAa,EAAE,CAAC;QAE3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,QAAa,EAAE,EAAE;YAClC,0EAA0E;YAC1E,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC3C,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,QAAQ,IAAI,EAAE,CAAC;gBAC1D,SAAS,CAAC,QAAQ,EAAE,CAAC,IAA2B,EAAE,IAAI,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,0EAA0E;YAC1E,IAAI,QAAQ,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;gBACxC,MAAM,QAAQ,GAAa,QAAQ,CAAC,WAAW,EAAE,WAAW,IAAI,EAAE,CAAC;gBACnE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,0EAA0E;YAC1E,IAAI,QAAQ,CAAC,QAAQ,KAAK,cAAc,EAAE,CAAC;gBACzC,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,IAAI,GAAG,CAAC,CAAC;gBACvE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAA,yBAAe,EAAC,GAAG,CAAC,CAAC,CAAC;gBACvD,OAAO,GAAG,EAAE,CAAC;gBACb,SAAS,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;gBACxC,OAAO;YACT,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAsB,EAAE,EAAE;YAC5C,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBACvC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACpB,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,OAAO;YACL,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE;SAC9B,CAAC;IACJ,CAAC;IAED,gFAAgF;IAEhF;;;OAGG;IACH,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;CACF;AAtOD,0CAsOC"}