@provablehq/veil-aleo-devnode 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Provable Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @provablehq/veil-aleo-devnode
2
+
3
+ Runs and drives a local Aleo development node from TypeScript. Reach for it in
4
+ tests, CI, and local development when you need a real node to broadcast against
5
+ without touching testnet — start one, mine blocks on demand, and tear it down.
6
+
7
+ The node is seeded with a well-known genesis account (`DEVNODE_PRIVATE_KEY`)
8
+ that holds credits, so you have a funded key to pay fees from the moment it
9
+ boots.
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ pnpm add @provablehq/veil-aleo-devnode @provablehq/veil-core
15
+ ```
16
+
17
+ `@provablehq/veil-aleo-devnode` drives the `aleo-devnode` binary as a subprocess — it does not
18
+ bundle a node. The binary MUST be installed and resolvable on `PATH` (override
19
+ its location per call with `devnodePath`). Every function throws with an
20
+ install-and-PATH hint if it cannot find or run the binary.
21
+
22
+ ## Usage
23
+
24
+ `startDevnode` spawns the node and resolves once its REST API answers, so the
25
+ returned instance is ready to receive transactions. Call `stop` to terminate it
26
+ (SIGTERM); starting again on the same socket shuts down any node already there
27
+ first.
28
+
29
+ ```ts
30
+ import { startDevnode, advanceDevnode, DEVNODE_ADDR } from '@provablehq/veil-aleo-devnode'
31
+
32
+ const devnode = await startDevnode({
33
+ socketAddr: DEVNODE_ADDR, // '127.0.0.1:3030'
34
+ storagePath: '', // '' → default ./devnode dir; omit for in-memory
35
+ })
36
+
37
+ // ... broadcast transactions against http://127.0.0.1:3030 ...
38
+
39
+ await advanceDevnode({ numBlocks: 1 }) // mine a block so a broadcast finalizes
40
+
41
+ await devnode.stop()
42
+ ```
43
+
44
+ `advanceDevnode` mines blocks on a running node — pair it with
45
+ `manualBlockCreation` on `startDevnode` when you want deterministic block
46
+ timing instead of automatic creation. `restoreDevnode` reloads ledger state
47
+ from a named snapshot, optionally restarting the node afterward.
48
+
49
+ Taking a snapshot is a live REST call against the running node, not a binary
50
+ subcommand, so it lives on the `@provablehq/veil-core` test client as `snapshot` (with
51
+ `listSnapshots` to enumerate them) rather than in this package. Capture state
52
+ with `client.snapshot(...)` and reload it here with `restoreDevnode` — the node
53
+ must have been started with `storagePath`, since an in-memory node has nothing
54
+ to snapshot.
55
+
56
+ ### As test-client actions
57
+
58
+ `devnodeActions` folds the process-lifecycle functions onto a `@provablehq/veil-core` test
59
+ client via `.extend`, so a single client drives the node (start/advance/restore)
60
+ and, through the core test actions, snapshots it.
61
+
62
+ ```ts
63
+ import { createTestClient, http } from '@provablehq/veil-core'
64
+ import { devnodeActions } from '@provablehq/veil-aleo-devnode'
65
+
66
+ const client = createTestClient({
67
+ transport: http('http://127.0.0.1:3030', { network: 'testnet' }),
68
+ }).extend(devnodeActions)
69
+
70
+ const devnode = await client.startDevnode({ storagePath: '' })
71
+ await client.advanceDevnode({ numBlocks: 1 })
72
+ const { name } = await client.snapshot({ name: 'before-deploy' }) // core test action
73
+ // ...run something you may want to undo...
74
+ await client.restoreDevnode({ snapshot: name, restart: true })
75
+ await devnode.stop()
76
+ ```
77
+
78
+ ## Exports
79
+
80
+ - `startDevnode(options?)` — spawn a node; resolves to a `DevnodeInstance` once
81
+ the REST API is ready.
82
+ - `advanceDevnode(options?)` — mine one or more blocks on a running node.
83
+ - `restoreDevnode(options)` — restore ledger state from a snapshot.
84
+ - `devnodeActions` — `.extend` decorator that adds the above to a client.
85
+ - `DEVNODE_PRIVATE_KEY` — the seeded, funded genesis account key.
86
+ - `DEVNODE_ADDR` — the default socket address, `127.0.0.1:3030`.
87
+
88
+ See the JSDoc on `DevnodeStartOptions`, `DevnodeAdvanceOptions`, and
89
+ `DevnodeRestoreOptions` for every flag, its default, and the CLI switch it maps
90
+ to.
@@ -0,0 +1,176 @@
1
+ import { Client } from '@provablehq/veil-core';
2
+
3
+ /** The well-known seeded private key used by Aleo Devnode */
4
+ declare const DEVNODE_PRIVATE_KEY = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH";
5
+ /** Default local devnode socket address */
6
+ declare const DEVNODE_ADDR = "127.0.0.1:3030";
7
+ /**
8
+ * Options for {@link startDevnode}.
9
+ *
10
+ * Most fields map to flags of the `aleo-devnode start` subcommand.
11
+ */
12
+ type DevnodeStartOptions = {
13
+ /** Private key for block creation. Defaults to `DEVNODE_PRIVATE_KEY`. */
14
+ privateKey?: string;
15
+ /** `-a, --socket-addr`. REST API bind address. Defaults to `DEVNODE_ADDR`. */
16
+ socketAddr?: string;
17
+ /** `-v, --verbosity` (0–2). Defaults to 2. */
18
+ verbosity?: 0 | 1 | 2;
19
+ /** `-g, --genesis-path`. Path to a custom genesis block file. */
20
+ genesisPath?: string;
21
+ /**
22
+ * `-s, --storage [DIR]`. Directory for persistent ledger storage.
23
+ * - Omit for in-memory (ephemeral).
24
+ * - Pass an empty string to use the default "./devnode" directory.
25
+ * - Pass a path string for a custom directory.
26
+ */
27
+ storagePath?: string;
28
+ /** `-c, --clear-storage`. Clear the storage directory before starting. Requires `storagePath`. */
29
+ clearStorage?: boolean;
30
+ /** `-m, --manual-block-creation`. Disable automatic block creation after broadcast. */
31
+ manualBlockCreation?: boolean;
32
+ /** Milliseconds to wait for the REST API to become ready. Defaults to 30000. */
33
+ readyTimeout?: number;
34
+ /** Path to the aleo-devnode binary. Defaults to `'aleo-devnode'` (resolved on PATH). */
35
+ devnodePath?: string;
36
+ /** Write devnode stdout/stderr to devnode-<port>.log in the current directory. Defaults to false. */
37
+ verbose?: boolean;
38
+ };
39
+ /**
40
+ * Options for {@link advanceDevnode}.
41
+ *
42
+ * Fields map to the `aleo-devnode advance` subcommand.
43
+ */
44
+ type DevnodeAdvanceOptions = {
45
+ /** Number of blocks to advance. Defaults to 1. */
46
+ numBlocks?: number;
47
+ /** `--socket-addr`. Target devnode socket. Defaults to `DEVNODE_ADDR`. */
48
+ socketAddr?: string;
49
+ /** Path to the aleo-devnode binary. Defaults to `'aleo-devnode'` (resolved on PATH). */
50
+ devnodePath?: string;
51
+ };
52
+ /**
53
+ * Options for {@link restoreDevnode}.
54
+ *
55
+ * Fields map to flags of the `aleo-devnode restore` subcommand. Restoring
56
+ * requires persistent storage, so the snapshot must have been taken from a
57
+ * devnode started with `storagePath`.
58
+ */
59
+ type DevnodeRestoreOptions = {
60
+ /** `--snapshot`. Name of the snapshot to restore. Required. */
61
+ snapshot: string;
62
+ /** `--storage`. Ledger storage directory to restore into. Defaults to `'devnode'`. */
63
+ storage?: string;
64
+ /** `--restart`. Restart the devnode after restoring. */
65
+ restart?: boolean;
66
+ /** `--private-key`. Required when `restart` is true (or set via `$PRIVATE_KEY`). */
67
+ privateKey?: string;
68
+ /** `-a, --socket-addr`. Forwarded to `start` when `restart` is true. */
69
+ socketAddr?: string;
70
+ /** `-v, --verbosity`. Forwarded to `start` when `restart` is true. */
71
+ verbosity?: 0 | 1 | 2;
72
+ /** `-m, --manual-block-creation`. Forwarded to `start` when `restart` is true. */
73
+ manualBlockCreation?: boolean;
74
+ /** Path to the aleo-devnode binary. Defaults to `'aleo-devnode'` (resolved on PATH). */
75
+ devnodePath?: string;
76
+ };
77
+ /**
78
+ * Handle to a running devnode process returned by {@link startDevnode}.
79
+ *
80
+ * Hold on to it for the lifetime of the node and call `stop()` when done —
81
+ * the child process is not stopped automatically when the parent exits.
82
+ */
83
+ type DevnodeInstance = {
84
+ /** Socket address the devnode is listening on. */
85
+ socketAddr: string;
86
+ /** Terminates the devnode process gracefully (SIGTERM). */
87
+ stop: () => Promise<void>;
88
+ };
89
+ /**
90
+ * Starts a local Aleo devnode and waits until its REST API answers.
91
+ *
92
+ * Spawns the `aleo-devnode` binary as a child process, so it MUST be
93
+ * installed and on PATH (or located via `devnodePath`). If a devnode is
94
+ * already listening on the target socket, it is asked to shut down first so
95
+ * the new instance can bind. Resolves once the node serves block height, or
96
+ * rejects after `readyTimeout`.
97
+ *
98
+ * By default the node binds `127.0.0.1:3030`, keeps its ledger in memory
99
+ * (lost on stop), creates blocks automatically, and produces blocks with the
100
+ * well-known seeded key {@link DEVNODE_PRIVATE_KEY}.
101
+ *
102
+ * @param options Overrides for the defaults above; omit for an ephemeral
103
+ * node on port 3030.
104
+ * @returns A {@link DevnodeInstance} — keep it and call `stop()` to terminate
105
+ * the process.
106
+ * @throws If the binary is missing, the process exits during startup, or the
107
+ * REST API is not ready within `readyTimeout` (default 30000 ms).
108
+ *
109
+ * @example
110
+ * import { startDevnode } from '@provablehq/veil-aleo-devnode'
111
+ *
112
+ * const devnode = await startDevnode()
113
+ * // ...run tests against http://127.0.0.1:3030...
114
+ * await devnode.stop()
115
+ */
116
+ declare function startDevnode(options?: DevnodeStartOptions): Promise<DevnodeInstance>;
117
+ /**
118
+ * Advances a running devnode by one or more empty blocks.
119
+ *
120
+ * Spawns `aleo-devnode advance` as a child process (requires the binary on
121
+ * PATH) and resolves when it exits. Use it to move the chain forward when the
122
+ * node runs with `manualBlockCreation`, or when a test needs height to pass.
123
+ *
124
+ * @param options.numBlocks Blocks to produce. Defaults to 1.
125
+ * @param options.socketAddr Devnode to target. Defaults to `127.0.0.1:3030`.
126
+ * @throws If the binary is missing or no devnode answers on the socket.
127
+ */
128
+ declare function advanceDevnode(options?: DevnodeAdvanceOptions): Promise<void>;
129
+ /**
130
+ * Restores a devnode ledger from a named snapshot.
131
+ *
132
+ * Spawns `aleo-devnode restore` as a child process (requires the binary on
133
+ * PATH). With `restart: true` the devnode is relaunched on the restored
134
+ * ledger; otherwise only the storage directory is rewritten and the caller
135
+ * starts the node separately.
136
+ *
137
+ * @param options Snapshot name, target storage directory, and optional
138
+ * restart parameters.
139
+ * @throws If the binary is missing, the snapshot does not exist, or the
140
+ * command exits non-zero.
141
+ */
142
+ declare function restoreDevnode(options: DevnodeRestoreOptions): Promise<void>;
143
+ /**
144
+ * Devnode management actions added to a test client by {@link devnodeActions}.
145
+ *
146
+ * Each action delegates to the standalone function of the same name and
147
+ * spawns the `aleo-devnode` binary.
148
+ *
149
+ * @property startDevnode Starts a devnode; see {@link startDevnode}.
150
+ * @property advanceDevnode Produces blocks on a running devnode; see {@link advanceDevnode}.
151
+ * @property restoreDevnode Restores a ledger snapshot; see {@link restoreDevnode}.
152
+ */
153
+ type DevnodeClientActions = {
154
+ startDevnode: (options?: DevnodeStartOptions) => Promise<DevnodeInstance>;
155
+ advanceDevnode: (options?: DevnodeAdvanceOptions) => Promise<void>;
156
+ restoreDevnode: (options: DevnodeRestoreOptions) => Promise<void>;
157
+ };
158
+ /**
159
+ * Adds devnode management actions to a test client via `.extend`.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * import { createTestClient, http } from '@provablehq/veil-core'
164
+ * import { devnodeActions } from '@provablehq/veil-aleo-devnode'
165
+ *
166
+ * const client = createTestClient({ transport: http('http://127.0.0.1:3030', { network: 'testnet' }) })
167
+ * .extend(devnodeActions)
168
+ *
169
+ * const devnode = await client.startDevnode()
170
+ * await client.advanceDevnode({ numBlocks: 1 })
171
+ * await devnode.stop()
172
+ * ```
173
+ */
174
+ declare function devnodeActions(_client: Client): DevnodeClientActions;
175
+
176
+ export { DEVNODE_ADDR, DEVNODE_PRIVATE_KEY, type DevnodeAdvanceOptions, type DevnodeClientActions, type DevnodeInstance, type DevnodeRestoreOptions, type DevnodeStartOptions, advanceDevnode, devnodeActions, restoreDevnode, startDevnode };
package/dist/index.js ADDED
@@ -0,0 +1,170 @@
1
+ // src/index.ts
2
+ import { spawn } from "child_process";
3
+ import { createWriteStream } from "fs";
4
+ import { join } from "path";
5
+ var DEVNODE_PRIVATE_KEY = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH";
6
+ var DEVNODE_ADDR = "127.0.0.1:3030";
7
+ var HEALTH_CHECK_PATH = "/testnet/block/height/latest";
8
+ var HEALTH_CHECK_INTERVAL_MS = 250;
9
+ var HEALTH_CHECK_REQUEST_TIMEOUT_MS = 1e3;
10
+ async function startDevnode(options) {
11
+ const privateKey = options?.privateKey ?? DEVNODE_PRIVATE_KEY;
12
+ const socketAddr = options?.socketAddr ?? DEVNODE_ADDR;
13
+ const verbosity = options?.verbosity ?? 2;
14
+ const readyTimeout = options?.readyTimeout ?? 3e4;
15
+ const devnodePath = options?.devnodePath ?? "aleo-devnode";
16
+ const verbose = options?.verbose ?? false;
17
+ await tryShutdownExisting(socketAddr);
18
+ const args = [
19
+ "start",
20
+ "--private-key",
21
+ privateKey,
22
+ "--socket-addr",
23
+ socketAddr,
24
+ "--verbosity",
25
+ String(verbosity)
26
+ ];
27
+ if (options?.genesisPath !== void 0) args.push("--genesis-path", options.genesisPath);
28
+ if (options?.storagePath !== void 0) {
29
+ args.push(options.storagePath === "" ? "--storage" : `--storage=${options.storagePath}`);
30
+ }
31
+ if (options?.clearStorage) args.push("--clear-storage");
32
+ if (options?.manualBlockCreation) args.push("--manual-block-creation");
33
+ return spawnDevnode(devnodePath, args, socketAddr, readyTimeout, verbose);
34
+ }
35
+ async function advanceDevnode(options) {
36
+ const devnodePath = options?.devnodePath ?? "aleo-devnode";
37
+ const args = ["advance"];
38
+ if (options?.numBlocks !== void 0) args.push(String(options.numBlocks));
39
+ if (options?.socketAddr) args.push("--socket-addr", options.socketAddr);
40
+ await runDevnode(devnodePath, args);
41
+ }
42
+ async function restoreDevnode(options) {
43
+ const devnodePath = options.devnodePath ?? "aleo-devnode";
44
+ const args = ["restore", "--snapshot", options.snapshot];
45
+ if (options.storage) args.push("--storage", options.storage);
46
+ if (options.restart) {
47
+ args.push("--restart");
48
+ if (options.privateKey) args.push("--private-key", options.privateKey);
49
+ if (options.socketAddr) args.push("--socket-addr", options.socketAddr);
50
+ if (options.verbosity !== void 0) args.push("--verbosity", String(options.verbosity));
51
+ if (options.manualBlockCreation) args.push("--manual-block-creation");
52
+ }
53
+ await runDevnode(devnodePath, args);
54
+ }
55
+ async function tryShutdownExisting(socketAddr) {
56
+ const baseUrl = `http://${socketAddr}`;
57
+ try {
58
+ const res = await fetch(`${baseUrl}/testnet/shutdown`, {
59
+ method: "POST",
60
+ signal: AbortSignal.timeout(2e3)
61
+ });
62
+ if (!res.ok) return;
63
+ const deadline = Date.now() + 5e3;
64
+ while (Date.now() < deadline) {
65
+ try {
66
+ await fetch(`${baseUrl}${HEALTH_CHECK_PATH}`, { signal: AbortSignal.timeout(500) });
67
+ await new Promise((r) => setTimeout(r, 200));
68
+ } catch {
69
+ return;
70
+ }
71
+ }
72
+ } catch {
73
+ }
74
+ }
75
+ function runDevnode(devnodePath, args) {
76
+ return new Promise((resolve, reject) => {
77
+ const proc = spawn(devnodePath, args, { stdio: "inherit" });
78
+ proc.on(
79
+ "error",
80
+ (err) => reject(
81
+ new Error(
82
+ `Failed to run ${devnodePath}: ${err.message}. Ensure aleo-devnode is installed and on PATH.`
83
+ )
84
+ )
85
+ );
86
+ proc.on("exit", (code) => {
87
+ if (code === 0) resolve();
88
+ else reject(new Error(`${devnodePath} ${args[0]} exited with code ${code}`));
89
+ });
90
+ });
91
+ }
92
+ async function spawnDevnode(devnodePath, args, socketAddr, readyTimeout, verbose = false) {
93
+ const proc = spawn(devnodePath, args, {
94
+ stdio: "pipe",
95
+ // MUST mirror DEVNODE_CONSENSUS_HEIGHTS in @provablehq/veil-aleo-sdk so the
96
+ // transaction builder and the node agree on active consensus versions.
97
+ env: { ...process.env, CONSENSUS_VERSION_HEIGHTS: process.env.CONSENSUS_VERSION_HEIGHTS || "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16" }
98
+ });
99
+ if (verbose) {
100
+ const port = socketAddr.split(":")[1] ?? socketAddr.replace(/\./g, "-");
101
+ const logFile = join(process.cwd(), `devnode-${port}.log`);
102
+ const logStream = createWriteStream(logFile, { flags: "w" });
103
+ proc.stdout?.pipe(logStream);
104
+ proc.stderr?.pipe(logStream);
105
+ console.log(`[devnode] logs \u2192 ${logFile}`);
106
+ } else {
107
+ proc.stdout?.resume();
108
+ proc.stderr?.resume();
109
+ }
110
+ let startError;
111
+ proc.on("error", (err) => {
112
+ startError = new Error(
113
+ `Failed to start ${devnodePath}: ${err.message}. Ensure aleo-devnode is installed and on PATH.`
114
+ );
115
+ });
116
+ proc.on("exit", (code, signal) => {
117
+ if (signal === "SIGTERM" || signal === "SIGINT") return;
118
+ if (code !== 0 && code !== null) {
119
+ startError = startError ?? new Error(`${devnodePath} exited unexpectedly with code ${code}`);
120
+ }
121
+ });
122
+ try {
123
+ await waitForReady(`http://${socketAddr}`, readyTimeout, () => startError);
124
+ } catch (err) {
125
+ proc.kill("SIGTERM");
126
+ throw err;
127
+ }
128
+ return {
129
+ socketAddr,
130
+ stop: () => new Promise((resolve) => {
131
+ proc.kill("SIGTERM");
132
+ proc.once("exit", () => resolve());
133
+ })
134
+ };
135
+ }
136
+ function devnodeActions(_client) {
137
+ return {
138
+ startDevnode: (options) => startDevnode(options),
139
+ advanceDevnode: (options) => advanceDevnode(options),
140
+ restoreDevnode: (options) => restoreDevnode(options)
141
+ };
142
+ }
143
+ async function waitForReady(baseUrl, timeout, getError) {
144
+ const deadline = Date.now() + timeout;
145
+ const healthUrl = `${baseUrl}${HEALTH_CHECK_PATH}`;
146
+ while (Date.now() < deadline) {
147
+ const err = getError();
148
+ if (err) throw err;
149
+ try {
150
+ const response = await fetch(healthUrl, {
151
+ signal: AbortSignal.timeout(HEALTH_CHECK_REQUEST_TIMEOUT_MS)
152
+ });
153
+ if (response.ok) return;
154
+ } catch {
155
+ }
156
+ await new Promise((resolve) => setTimeout(resolve, HEALTH_CHECK_INTERVAL_MS));
157
+ }
158
+ throw new Error(
159
+ `Devnode at ${baseUrl} did not become ready within ${timeout}ms. Try increasing readyTimeout or check that aleo-devnode is installed and working.`
160
+ );
161
+ }
162
+ export {
163
+ DEVNODE_ADDR,
164
+ DEVNODE_PRIVATE_KEY,
165
+ advanceDevnode,
166
+ devnodeActions,
167
+ restoreDevnode,
168
+ startDevnode
169
+ };
170
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { spawn } from 'node:child_process'\nimport { createWriteStream } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Client } from '@provablehq/veil-core'\n\n/** The well-known seeded private key used by Aleo Devnode */\nexport const DEVNODE_PRIVATE_KEY = 'APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH'\n\n/** Default local devnode socket address */\nexport const DEVNODE_ADDR = '127.0.0.1:3030'\n\nconst HEALTH_CHECK_PATH = '/testnet/block/height/latest'\nconst HEALTH_CHECK_INTERVAL_MS = 250\nconst HEALTH_CHECK_REQUEST_TIMEOUT_MS = 1_000\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Options for {@link startDevnode}.\n *\n * Most fields map to flags of the `aleo-devnode start` subcommand.\n */\nexport type DevnodeStartOptions = {\n /** Private key for block creation. Defaults to `DEVNODE_PRIVATE_KEY`. */\n privateKey?: string\n /** `-a, --socket-addr`. REST API bind address. Defaults to `DEVNODE_ADDR`. */\n socketAddr?: string\n /** `-v, --verbosity` (0–2). Defaults to 2. */\n verbosity?: 0 | 1 | 2\n /** `-g, --genesis-path`. Path to a custom genesis block file. */\n genesisPath?: string\n /**\n * `-s, --storage [DIR]`. Directory for persistent ledger storage.\n * - Omit for in-memory (ephemeral).\n * - Pass an empty string to use the default \"./devnode\" directory.\n * - Pass a path string for a custom directory.\n */\n storagePath?: string\n /** `-c, --clear-storage`. Clear the storage directory before starting. Requires `storagePath`. */\n clearStorage?: boolean\n /** `-m, --manual-block-creation`. Disable automatic block creation after broadcast. */\n manualBlockCreation?: boolean\n /** Milliseconds to wait for the REST API to become ready. Defaults to 30000. */\n readyTimeout?: number\n /** Path to the aleo-devnode binary. Defaults to `'aleo-devnode'` (resolved on PATH). */\n devnodePath?: string\n /** Write devnode stdout/stderr to devnode-<port>.log in the current directory. Defaults to false. */\n verbose?: boolean\n}\n\n/**\n * Options for {@link advanceDevnode}.\n *\n * Fields map to the `aleo-devnode advance` subcommand.\n */\nexport type DevnodeAdvanceOptions = {\n /** Number of blocks to advance. Defaults to 1. */\n numBlocks?: number\n /** `--socket-addr`. Target devnode socket. Defaults to `DEVNODE_ADDR`. */\n socketAddr?: string\n /** Path to the aleo-devnode binary. Defaults to `'aleo-devnode'` (resolved on PATH). */\n devnodePath?: string\n}\n\n/**\n * Options for {@link restoreDevnode}.\n *\n * Fields map to flags of the `aleo-devnode restore` subcommand. Restoring\n * requires persistent storage, so the snapshot must have been taken from a\n * devnode started with `storagePath`.\n */\nexport type DevnodeRestoreOptions = {\n /** `--snapshot`. Name of the snapshot to restore. Required. */\n snapshot: string\n /** `--storage`. Ledger storage directory to restore into. Defaults to `'devnode'`. */\n storage?: string\n /** `--restart`. Restart the devnode after restoring. */\n restart?: boolean\n /** `--private-key`. Required when `restart` is true (or set via `$PRIVATE_KEY`). */\n privateKey?: string\n /** `-a, --socket-addr`. Forwarded to `start` when `restart` is true. */\n socketAddr?: string\n /** `-v, --verbosity`. Forwarded to `start` when `restart` is true. */\n verbosity?: 0 | 1 | 2\n /** `-m, --manual-block-creation`. Forwarded to `start` when `restart` is true. */\n manualBlockCreation?: boolean\n /** Path to the aleo-devnode binary. Defaults to `'aleo-devnode'` (resolved on PATH). */\n devnodePath?: string\n}\n\n/**\n * Handle to a running devnode process returned by {@link startDevnode}.\n *\n * Hold on to it for the lifetime of the node and call `stop()` when done —\n * the child process is not stopped automatically when the parent exits.\n */\nexport type DevnodeInstance = {\n /** Socket address the devnode is listening on. */\n socketAddr: string\n /** Terminates the devnode process gracefully (SIGTERM). */\n stop: () => Promise<void>\n}\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Starts a local Aleo devnode and waits until its REST API answers.\n *\n * Spawns the `aleo-devnode` binary as a child process, so it MUST be\n * installed and on PATH (or located via `devnodePath`). If a devnode is\n * already listening on the target socket, it is asked to shut down first so\n * the new instance can bind. Resolves once the node serves block height, or\n * rejects after `readyTimeout`.\n *\n * By default the node binds `127.0.0.1:3030`, keeps its ledger in memory\n * (lost on stop), creates blocks automatically, and produces blocks with the\n * well-known seeded key {@link DEVNODE_PRIVATE_KEY}.\n *\n * @param options Overrides for the defaults above; omit for an ephemeral\n * node on port 3030.\n * @returns A {@link DevnodeInstance} — keep it and call `stop()` to terminate\n * the process.\n * @throws If the binary is missing, the process exits during startup, or the\n * REST API is not ready within `readyTimeout` (default 30000 ms).\n *\n * @example\n * import { startDevnode } from '@provablehq/veil-aleo-devnode'\n *\n * const devnode = await startDevnode()\n * // ...run tests against http://127.0.0.1:3030...\n * await devnode.stop()\n */\nexport async function startDevnode(options?: DevnodeStartOptions): Promise<DevnodeInstance> {\n const privateKey = options?.privateKey ?? DEVNODE_PRIVATE_KEY\n const socketAddr = options?.socketAddr ?? DEVNODE_ADDR\n const verbosity = options?.verbosity ?? 2\n const readyTimeout = options?.readyTimeout ?? 30_000\n const devnodePath = options?.devnodePath ?? 'aleo-devnode'\n const verbose = options?.verbose ?? false\n\n await tryShutdownExisting(socketAddr)\n\n const args = [\n 'start',\n '--private-key', privateKey,\n '--socket-addr', socketAddr,\n '--verbosity', String(verbosity),\n ]\n\n if (options?.genesisPath !== undefined) args.push('--genesis-path', options.genesisPath)\n if (options?.storagePath !== undefined) {\n args.push(options.storagePath === '' ? '--storage' : `--storage=${options.storagePath}`)\n }\n if (options?.clearStorage) args.push('--clear-storage')\n if (options?.manualBlockCreation) args.push('--manual-block-creation')\n\n return spawnDevnode(devnodePath, args, socketAddr, readyTimeout, verbose)\n}\n\n/**\n * Advances a running devnode by one or more empty blocks.\n *\n * Spawns `aleo-devnode advance` as a child process (requires the binary on\n * PATH) and resolves when it exits. Use it to move the chain forward when the\n * node runs with `manualBlockCreation`, or when a test needs height to pass.\n *\n * @param options.numBlocks Blocks to produce. Defaults to 1.\n * @param options.socketAddr Devnode to target. Defaults to `127.0.0.1:3030`.\n * @throws If the binary is missing or no devnode answers on the socket.\n */\nexport async function advanceDevnode(options?: DevnodeAdvanceOptions): Promise<void> {\n const devnodePath = options?.devnodePath ?? 'aleo-devnode'\n const args = ['advance']\n if (options?.numBlocks !== undefined) args.push(String(options.numBlocks))\n if (options?.socketAddr) args.push('--socket-addr', options.socketAddr)\n await runDevnode(devnodePath, args)\n}\n\n/**\n * Restores a devnode ledger from a named snapshot.\n *\n * Spawns `aleo-devnode restore` as a child process (requires the binary on\n * PATH). With `restart: true` the devnode is relaunched on the restored\n * ledger; otherwise only the storage directory is rewritten and the caller\n * starts the node separately.\n *\n * @param options Snapshot name, target storage directory, and optional\n * restart parameters.\n * @throws If the binary is missing, the snapshot does not exist, or the\n * command exits non-zero.\n */\nexport async function restoreDevnode(options: DevnodeRestoreOptions): Promise<void> {\n const devnodePath = options.devnodePath ?? 'aleo-devnode'\n const args = ['restore', '--snapshot', options.snapshot]\n if (options.storage) args.push('--storage', options.storage)\n if (options.restart) {\n args.push('--restart')\n if (options.privateKey) args.push('--private-key', options.privateKey)\n if (options.socketAddr) args.push('--socket-addr', options.socketAddr)\n if (options.verbosity !== undefined) args.push('--verbosity', String(options.verbosity))\n if (options.manualBlockCreation) args.push('--manual-block-creation')\n }\n await runDevnode(devnodePath, args)\n}\n\n// =============================================================================\n// Subprocess helpers\n// =============================================================================\n\nasync function tryShutdownExisting(socketAddr: string): Promise<void> {\n const baseUrl = `http://${socketAddr}`\n try {\n const res = await fetch(`${baseUrl}/testnet/shutdown`, {\n method: 'POST',\n signal: AbortSignal.timeout(2_000),\n })\n if (!res.ok) return\n // Wait up to 5s for the old process to stop responding\n const deadline = Date.now() + 5_000\n while (Date.now() < deadline) {\n try {\n await fetch(`${baseUrl}${HEALTH_CHECK_PATH}`, { signal: AbortSignal.timeout(500) })\n await new Promise<void>(r => setTimeout(r, 200))\n } catch {\n return // port no longer responding — old devnode is gone\n }\n }\n } catch {\n // nothing was listening on the port — proceed normally\n }\n}\n\nfunction runDevnode(devnodePath: string, args: string[]): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(devnodePath, args, { stdio: 'inherit' })\n proc.on('error', (err) =>\n reject(\n new Error(\n `Failed to run ${devnodePath}: ${err.message}. Ensure aleo-devnode is installed and on PATH.`,\n ),\n ),\n )\n proc.on('exit', (code) => {\n if (code === 0) resolve()\n else reject(new Error(`${devnodePath} ${args[0]} exited with code ${code}`))\n })\n })\n}\n\nasync function spawnDevnode(\n devnodePath: string,\n args: string[],\n socketAddr: string,\n readyTimeout: number,\n verbose: boolean = false,\n): Promise<DevnodeInstance> {\n const proc = spawn(devnodePath, args, {\n stdio: 'pipe',\n // MUST mirror DEVNODE_CONSENSUS_HEIGHTS in @provablehq/veil-aleo-sdk so the\n // transaction builder and the node agree on active consensus versions.\n env: { ...process.env, CONSENSUS_VERSION_HEIGHTS: process.env.CONSENSUS_VERSION_HEIGHTS || '0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16' },\n })\n\n if (verbose) {\n const port = socketAddr.split(':')[1] ?? socketAddr.replace(/\\./g, '-')\n const logFile = join(process.cwd(), `devnode-${port}.log`)\n const logStream = createWriteStream(logFile, { flags: 'w' })\n proc.stdout?.pipe(logStream)\n proc.stderr?.pipe(logStream)\n console.log(`[devnode] logs → ${logFile}`)\n } else {\n proc.stdout?.resume()\n proc.stderr?.resume()\n }\n\n let startError: Error | undefined\n proc.on('error', (err) => {\n startError = new Error(\n `Failed to start ${devnodePath}: ${err.message}. Ensure aleo-devnode is installed and on PATH.`,\n )\n })\n proc.on('exit', (code, signal) => {\n if (signal === 'SIGTERM' || signal === 'SIGINT') return\n if (code !== 0 && code !== null) {\n startError = startError ?? new Error(`${devnodePath} exited unexpectedly with code ${code}`)\n }\n })\n\n try {\n await waitForReady(`http://${socketAddr}`, readyTimeout, () => startError)\n } catch (err) {\n proc.kill('SIGTERM')\n throw err\n }\n\n return {\n socketAddr,\n stop: () =>\n new Promise<void>((resolve) => {\n proc.kill('SIGTERM')\n proc.once('exit', () => resolve())\n }),\n }\n}\n\n// =============================================================================\n// Test client decorator\n// =============================================================================\n\n/**\n * Devnode management actions added to a test client by {@link devnodeActions}.\n *\n * Each action delegates to the standalone function of the same name and\n * spawns the `aleo-devnode` binary.\n *\n * @property startDevnode Starts a devnode; see {@link startDevnode}.\n * @property advanceDevnode Produces blocks on a running devnode; see {@link advanceDevnode}.\n * @property restoreDevnode Restores a ledger snapshot; see {@link restoreDevnode}.\n */\nexport type DevnodeClientActions = {\n startDevnode: (options?: DevnodeStartOptions) => Promise<DevnodeInstance>\n advanceDevnode: (options?: DevnodeAdvanceOptions) => Promise<void>\n restoreDevnode: (options: DevnodeRestoreOptions) => Promise<void>\n}\n\n/**\n * Adds devnode management actions to a test client via `.extend`.\n *\n * @example\n * ```ts\n * import { createTestClient, http } from '@provablehq/veil-core'\n * import { devnodeActions } from '@provablehq/veil-aleo-devnode'\n *\n * const client = createTestClient({ transport: http('http://127.0.0.1:3030', { network: 'testnet' }) })\n * .extend(devnodeActions)\n *\n * const devnode = await client.startDevnode()\n * await client.advanceDevnode({ numBlocks: 1 })\n * await devnode.stop()\n * ```\n */\nexport function devnodeActions(_client: Client): DevnodeClientActions {\n return {\n startDevnode: (options) => startDevnode(options),\n advanceDevnode: (options) => advanceDevnode(options),\n restoreDevnode: (options) => restoreDevnode(options),\n }\n}\n\nasync function waitForReady(\n baseUrl: string,\n timeout: number,\n getError: () => Error | undefined,\n): Promise<void> {\n const deadline = Date.now() + timeout\n const healthUrl = `${baseUrl}${HEALTH_CHECK_PATH}`\n\n while (Date.now() < deadline) {\n const err = getError()\n if (err) throw err\n\n try {\n const response = await fetch(healthUrl, {\n signal: AbortSignal.timeout(HEALTH_CHECK_REQUEST_TIMEOUT_MS),\n })\n if (response.ok) return\n } catch {\n // not ready yet — keep polling\n }\n\n await new Promise<void>((resolve) => setTimeout(resolve, HEALTH_CHECK_INTERVAL_MS))\n }\n\n throw new Error(\n `Devnode at ${baseUrl} did not become ready within ${timeout}ms. ` +\n 'Try increasing readyTimeout or check that aleo-devnode is installed and working.',\n )\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@provablehq/veil-aleo-devnode",
3
+ "version": "0.4.0",
4
+ "description": "TypeScript interface for orchestrating local Aleo test networks for testing & developing Aleo programs.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ProvableHQ/veil.git",
9
+ "directory": "packages/devnode"
10
+ },
11
+ "homepage": "https://github.com/ProvableHQ/veil#readme",
12
+ "type": "module",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "sideEffects": false,
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "peerDependencies": {
32
+ "@provablehq/veil-core": "0.4.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "test": "vitest run",
37
+ "typecheck": "tsc --noEmit"
38
+ }
39
+ }