@effectstream/npm-midnight-indexer 0.104.1 → 0.200.1

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 CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  NPM wrapper that runs the [Midnight](https://midnight.network) Indexer either as a Docker container or as a native binary. Boots alongside `@effectstream/npm-midnight-node` and `@effectstream/npm-midnight-proof-server` to give Effectstream a local indexer to consume.
4
4
 
5
- - Pinned Midnight indexer v4.3.3.
5
+ - Pinned Midnight indexer `4.4.0-rc.1`.
6
+ - Native targets are `macos-arm64` and `linux-amd64`; no Linux arm64 asset is published.
6
7
  - Docker or binary mode, with platform-aware defaults (macOS arm64 and Linux can use either; Windows is Docker-only).
7
- - One env var to set: `APP__INFRA__SECRET`. Everything else has a default that works against the local Midnight stack.
8
+ - One env var to set: `APP__INFRA__SECRET`, as a hex-encoded 32-byte value (64 hex characters). Everything else has a default that works against the local Midnight stack.
8
9
  - Used by the orchestrator's Midnight step; sits in front of `MidnightFetcher` on the sync side.
9
10
  - Maps port 8088 (Docker) or runs on localhost (binary) so the rest of the local stack reaches it the same way.
10
11
 
@@ -24,13 +25,13 @@ Pick a mode and pass `APP__INFRA__SECRET`. Without a flag, the wrapper prompts i
24
25
 
25
26
  ```bash
26
27
  # Docker (recommended where available)
27
- APP__INFRA__SECRET=<secret> bunx npm-midnight-indexer --docker
28
+ APP__INFRA__SECRET=<64-hex-characters> bunx npm-midnight-indexer --docker
28
29
 
29
30
  # Native binary (Linux, macOS arm64)
30
- APP__INFRA__SECRET=<secret> bunx npm-midnight-indexer --binary
31
+ APP__INFRA__SECRET=<64-hex-characters> bunx npm-midnight-indexer --binary
31
32
 
32
33
  # Interactive: prompts for Docker vs binary
33
- APP__INFRA__SECRET=<secret> bunx npm-midnight-indexer
34
+ APP__INFRA__SECRET=<64-hex-characters> bunx npm-midnight-indexer
34
35
 
35
36
  # Help
36
37
  bunx npm-midnight-indexer --help
@@ -42,7 +43,7 @@ The Docker path pulls `midnightntwrk/indexer-standalone` and maps container port
42
43
 
43
44
  | Variable | Required | Docker default | Binary default | Purpose |
44
45
  | --- | --- | --- | --- | --- |
45
- | `APP__INFRA__SECRET` | yes | - | - | Indexer secret. |
46
+ | `APP__INFRA__SECRET` | yes | - | - | Hex-encoded 32-byte indexer secret. |
46
47
  | `LEDGER_NETWORK_ID` | no | `Undeployed` | `Undeployed` | Ledger network selector. |
47
48
  | `SUBSTRATE_NODE_WS_URL` | no | `ws://node:9944` | `ws://localhost:9944` | Substrate node WS. |
48
49
  | `FEATURES_WALLET_ENABLED` | no | `true` | `true` | Wallet features. |
@@ -55,7 +56,7 @@ The Docker path pulls `midnightntwrk/indexer-standalone` and maps container port
55
56
 
56
57
  ### Supported binary platforms
57
58
 
58
- Linux arm64, Linux amd64, macOS arm64.
59
+ Linux amd64 and macOS arm64.
59
60
 
60
61
  ## Inside Effectstream
61
62
 
package/binary.js CHANGED
@@ -4,7 +4,7 @@ const axios = require("axios");
4
4
  const extract = require("extract-zip");
5
5
  const path = require("path");
6
6
 
7
- const CURRENT_BINARY_VERSION = "v4.3.3";
7
+ const CURRENT_BINARY_VERSION = "v4.4.0-rc.1";
8
8
  const FINAL_BINARY_NAME = "indexer-standalone";
9
9
 
10
10
  /*
package/docker.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { spawn, exec } = require('child_process');
2
2
  const { promisify } = require('util');
3
3
  const execAsync = promisify(exec);
4
+ const IMAGE = 'midnightntwrk/indexer-standalone:4.4.0-rc.1';
4
5
 
5
6
  /**
6
7
  * Checks if Docker is installed and available on the system
@@ -20,12 +21,10 @@ async function checkIfDockerExists() {
20
21
  * @returns {Promise<void>}
21
22
  */
22
23
  async function pullDockerImage() {
23
- const imageName = 'midnightntwrk/indexer-standalone';
24
-
25
- console.log(`Pulling Docker image: ${imageName}`);
24
+ console.log(`Pulling Docker image: ${IMAGE}`);
26
25
 
27
26
  return new Promise((resolve, reject) => {
28
- const childProcess = spawn('docker', ['pull', imageName], {
27
+ const childProcess = spawn('docker', ['pull', IMAGE], {
29
28
  stdio: 'inherit'
30
29
  });
31
30
 
@@ -51,7 +50,6 @@ async function pullDockerImage() {
51
50
  * @returns {ChildProcess} The spawned Docker process
52
51
  */
53
52
  function runDockerContainer(env = process.env, args = []) {
54
- const imageName = 'midnightntwrk/indexer-standalone';
55
53
  const containerName = 'midnight-local-indexer';
56
54
 
57
55
  // Stop and remove existing container if it exists
@@ -124,7 +122,7 @@ function runDockerContainer(env = process.env, args = []) {
124
122
  });
125
123
 
126
124
  // Add the image name
127
- dockerArgs.push(imageName);
125
+ dockerArgs.push(IMAGE);
128
126
 
129
127
  // Add any remaining arguments
130
128
  if (args.length > 0) {
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { binary, getPlatform, cleanBinaries } = require("./binary");
2
- const { runMidnightIndexer, waitForNodeBlock } = require(
2
+ const { runMidnightIndexer, waitForNodeBlock, isValidIndexerSecret } = require(
3
3
  "./run_midnight_indexer",
4
4
  );
5
5
  const { checkIfDockerExists, pullDockerImage, runDockerContainer } = require(
@@ -66,7 +66,7 @@ Options:
66
66
  --help Show this help message
67
67
 
68
68
  Environment Variables:
69
- APP__INFRA__SECRET Required: Secret key for the application
69
+ APP__INFRA__SECRET Required: 64-character hexadecimal secret
70
70
  LEDGER_NETWORK_ID Optional: Ledger network ID (default: Undeployed)
71
71
  SUBSTRATE_NODE_WS_URL Optional: Substrate node WebSocket URL
72
72
  (Docker default: ws://node:9944, Binary default: ws://localhost:9944)
@@ -79,11 +79,11 @@ Environment Variables:
79
79
  Note: macOS Intel users will automatically use Docker. macOS arm64 supports both binary and Docker execution.
80
80
 
81
81
  Examples:
82
- APP__INFRA__SECRET=mysecret node index.js --docker # Use Docker
83
- APP__INFRA__SECRET=mysecret node index.js --binary # Use binary (if supported)
84
- APP__INFRA__SECRET=mysecret node index.js --clean-binaries # Delete and redownload binaries
82
+ APP__INFRA__SECRET=<64_hex_characters> node index.js --docker # Use Docker
83
+ APP__INFRA__SECRET=<64_hex_characters> node index.js --binary # Use binary (if supported)
84
+ APP__INFRA__SECRET=<64_hex_characters> node index.js --clean-binaries # Delete and redownload binaries
85
85
  node index.js --only-clean # Only delete downloaded binaries
86
- APP__INFRA__SECRET=mysecret node index.js --clean # Clean SQLite database
86
+ APP__INFRA__SECRET=<64_hex_characters> node index.js --clean # Clean SQLite database
87
87
  node index.js --help # Show this help
88
88
  `);
89
89
  }
@@ -170,9 +170,9 @@ async function runWithBinary(env, args, forceClean = false) {
170
170
  console.log("Using binary to run midnight indexer...");
171
171
 
172
172
  // Check for required environment variable
173
- if (!env.APP__INFRA__SECRET) {
174
- console.error("Error: APP__INFRA__SECRET environment variable is required");
175
- console.log("Please set APP__INFRA__SECRET=<your_secret> and run again");
173
+ if (!isValidIndexerSecret(env.APP__INFRA__SECRET)) {
174
+ console.error("Error: APP__INFRA__SECRET must be exactly 64 hexadecimal characters");
175
+ console.log("Please set APP__INFRA__SECRET=<64_hex_characters> and run again");
176
176
  process.exit(1);
177
177
  }
178
178
 
@@ -243,12 +243,12 @@ async function main(args) {
243
243
  }
244
244
 
245
245
  // Check for required environment variable
246
- if (!env.APP__INFRA__SECRET) {
246
+ if (!isValidIndexerSecret(env.APP__INFRA__SECRET)) {
247
247
  console.error(
248
- "Error: APP__INFRA__SECRET environment variable is required",
248
+ "Error: APP__INFRA__SECRET must be exactly 64 hexadecimal characters",
249
249
  );
250
250
  console.log(
251
- "Please set APP__INFRA__SECRET=<your_secret> when running with --docker",
251
+ "Please set APP__INFRA__SECRET=<64_hex_characters> when running with --docker",
252
252
  );
253
253
  process.exit(1);
254
254
  }
@@ -296,11 +296,11 @@ async function main(args) {
296
296
  );
297
297
 
298
298
  // Check for required environment variable
299
- if (!env.APP__INFRA__SECRET) {
299
+ if (!isValidIndexerSecret(env.APP__INFRA__SECRET)) {
300
300
  console.error(
301
- "Error: APP__INFRA__SECRET environment variable is required",
301
+ "Error: APP__INFRA__SECRET must be exactly 64 hexadecimal characters",
302
302
  );
303
- console.log("Please set APP__INFRA__SECRET=<your_secret> and run again");
303
+ console.log("Please set APP__INFRA__SECRET=<64_hex_characters> and run again");
304
304
  process.exit(1);
305
305
  }
306
306
 
@@ -312,12 +312,12 @@ async function main(args) {
312
312
  const useDocker = await promptUserForDockerChoice();
313
313
  if (useDocker) {
314
314
  // Check for required environment variable
315
- if (!env.APP__INFRA__SECRET) {
315
+ if (!isValidIndexerSecret(env.APP__INFRA__SECRET)) {
316
316
  console.error(
317
- "Error: APP__INFRA__SECRET environment variable is required",
317
+ "Error: APP__INFRA__SECRET must be exactly 64 hexadecimal characters",
318
318
  );
319
319
  console.log(
320
- "Please set APP__INFRA__SECRET=<your_secret> and run again",
320
+ "Please set APP__INFRA__SECRET=<64_hex_characters> and run again",
321
321
  );
322
322
  process.exit(1);
323
323
  }
@@ -326,7 +326,7 @@ async function main(args) {
326
326
  }
327
327
 
328
328
  // Default to binary execution (only on supported platforms)
329
- return runWithBinary(env, flags.remainingArgs, flags.clean);
329
+ return runWithBinary(env, flags.remainingArgs, flags.cleanBinaries);
330
330
  }
331
331
 
332
332
  // Handle unhandled promise rejections
@@ -5,6 +5,8 @@ application:
5
5
  blocks_buffer: 10
6
6
  caught_up_max_distance: 10
7
7
  caught_up_leeway: 5
8
+ gc_bound: "200ms"
9
+ ledger_state_retention: 1000
8
10
  active_wallets_query_delay: "500ms"
9
11
  active_wallets_ttl: "30m"
10
12
  transaction_batch_size: 50
@@ -54,6 +56,8 @@ infra:
54
56
  batch_size: 20
55
57
  contract_actions:
56
58
  batch_size: 20
59
+ contract_events:
60
+ batch_size: 20
57
61
  dust_generations:
58
62
  batch_size: 20
59
63
  dust_ledger_events:
@@ -71,6 +75,9 @@ infra:
71
75
  progress_update_interval: "30s"
72
76
  zswap_ledger_events:
73
77
  batch_size: 20
78
+ progress_cache:
79
+ max_capacity: 10000
80
+ time_to_live: "5s"
74
81
  quota:
75
82
  max_concurrent_per_connection: 20
76
83
  max_session_subscriptions_per_minute: 10
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@effectstream/npm-midnight-indexer",
3
- "version": "0.104.1",
3
+ "version": "0.200.1",
4
4
  "description": "Downloads and runs the Midnight Indexer. It needs a running Midnight Node",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
7
+ "test": "bun test run_midnight_indexer.test.js",
8
8
  "clean": "node index.js --clean-binaries",
9
9
  "clean-only": "node index.js --only-clean"
10
10
  },
@@ -23,7 +23,6 @@
23
23
  "url": "https://github.com/effectstream/effectstream/issues"
24
24
  },
25
25
  "supportedPlatforms": [
26
- "linux-arm64",
27
26
  "linux-amd64",
28
27
  "macos-arm64"
29
28
  ],
@@ -6,10 +6,14 @@ const axios = require("axios");
6
6
 
7
7
  const BINARY_NAME = "indexer-standalone";
8
8
 
9
+ function isValidIndexerSecret(secret) {
10
+ return typeof secret === "string" && /^[0-9a-fA-F]{64}$/.test(secret);
11
+ }
12
+
9
13
  /**
10
14
  * Waits until the Midnight node has produced at least `minBlock`.
11
15
  *
12
- * The v4.3.3 indexer bundles an spo-indexer that, on a fresh DB, reads block #1
16
+ * The v4.4.0-rc.1 indexer bundles an spo-indexer that, on a fresh DB, reads block #1
13
17
  * to anchor the first epoch and exits(1) — killing the whole indexer — if that
14
18
  * block does not exist yet. Gating startup on block #1 avoids that startup race.
15
19
  *
@@ -90,47 +94,81 @@ function ensureConfigExists(configPath, env) {
90
94
  const apiPort = env.APP__INFRA__API__PORT || 8088;
91
95
 
92
96
  const defaultConfig = `
93
- run_migrations: true
94
- network_id: &network_id "${networkId}"
95
-
96
- chain_indexer_application:
97
- network_id: *network_id
98
- blocks_buffer: 60
99
- save_zswap_state_after: 1000
100
- caught_up_max_distance: 60
101
- caught_up_leeway: 30
102
-
103
- wallet_indexer_application:
104
- network_id: *network_id
105
- active_wallets_repeat_delay: "100ms"
97
+ thread_stack_size: "24MiB"
98
+
99
+ application:
100
+ network_id: "${networkId.toLowerCase()}"
101
+ blocks_buffer: 10
102
+ caught_up_max_distance: 10
103
+ caught_up_leeway: 5
104
+ gc_bound: "200ms"
105
+ ledger_state_retention: 1000
106
+ active_wallets_query_delay: "500ms"
106
107
  active_wallets_ttl: "30m"
107
- transaction_batch_size: 10
108
+ transaction_batch_size: 50
109
+
110
+ spo:
111
+ interval: 5000
112
+ stake_refresh:
113
+ period_secs: 900
114
+ page_size: 100
115
+ max_rps: 2
108
116
 
109
117
  infra:
118
+ run_migrations: true
119
+ storage:
120
+ cnn_url: "${cnnUrl}"
121
+ ledger_db:
122
+ cache_size: "1kiB"
123
+ cnn_url: "${env.APP__INFRA__LEDGER_DB__CNN_URL || "./data/ledger-db.sqlite"}"
110
124
  node:
111
125
  url: "${nodeUrl}"
112
126
  reconnect_max_delay: "10s"
113
127
  reconnect_max_attempts: 30
114
-
115
- storage:
116
- cnn_url: "${cnnUrl}"
117
-
128
+ subscription_recovery_timeout: "30s"
129
+ spo_node:
130
+ url: "${env.APP__INFRA__SPO_NODE__URL || nodeUrl}"
131
+ reconnect_max_delay: "10s"
132
+ reconnect_max_attempts: 30
133
+ blockfrost_id: "${env.APP__INFRA__SPO_NODE__BLOCKFROST_ID || "dummy-not-using-spo"}"
118
134
  api:
119
- address: "0.0.0.0"
135
+ address: "${env.APP__INFRA__API__ADDRESS || "0.0.0.0"}"
120
136
  port: ${apiPort}
121
137
  request_body_limit: "1MiB"
122
138
  max_complexity: 200
123
139
  max_depth: 15
124
- network_id: *network_id
140
+ subscription:
141
+ blocks: { batch_size: 20 }
142
+ contract_actions: { batch_size: 20 }
143
+ contract_events: { batch_size: 20 }
144
+ dust_generations: { batch_size: 20 }
145
+ dust_ledger_events: { batch_size: 20 }
146
+ dust_nullifier_transactions: { batch_size: 20 }
147
+ shielded_nullifier_transactions: { batch_size: 20 }
148
+ shielded_transactions:
149
+ batch_size: 20
150
+ progress_update_interval: "30s"
151
+ keep_wallet_alive_interval: "1m"
152
+ unshielded_transactions:
153
+ batch_size: 20
154
+ progress_update_interval: "30s"
155
+ zswap_ledger_events: { batch_size: 20 }
156
+ progress_cache:
157
+ max_capacity: 10000
158
+ time_to_live: "5s"
159
+ quota:
160
+ max_concurrent_per_connection: 20
161
+ max_session_subscriptions_per_minute: 10
125
162
 
126
163
  telemetry:
127
164
  tracing:
128
165
  enabled: false
129
166
  service_name: "indexer"
167
+ otlp_exporter_endpoint: "http://localhost:4317"
130
168
  metrics:
131
169
  enabled: false
132
170
  address: "0.0.0.0"
133
- port: 9000
171
+ port: ${env.APP__TELEMETRY__METRICS__PORT || 9000}
134
172
  `;
135
173
 
136
174
  fs.writeFileSync(configPath, defaultConfig.trim());
@@ -298,4 +336,9 @@ function runMidnightIndexer(env = process.env, args = []) {
298
336
  return childProcess;
299
337
  }
300
338
 
301
- module.exports = { runMidnightIndexer, waitForNodeBlock };
339
+ module.exports = {
340
+ ensureConfigExists,
341
+ isValidIndexerSecret,
342
+ runMidnightIndexer,
343
+ waitForNodeBlock,
344
+ };
@@ -0,0 +1,59 @@
1
+ const fs = require("fs");
2
+ const os = require("os");
3
+ const path = require("path");
4
+
5
+ const { describe, expect, test } = require("bun:test");
6
+ const yaml = require("js-yaml");
7
+
8
+ const {
9
+ ensureConfigExists,
10
+ isValidIndexerSecret,
11
+ } = require("./run_midnight_indexer");
12
+
13
+ function expectRc1Schema(config) {
14
+ expect(config.application.gc_bound).toBe("200ms");
15
+ expect(config.application.ledger_state_retention).toBe(1000);
16
+ expect(config.infra.api.subscription.contract_events.batch_size).toBe(20);
17
+ expect(config.infra.api.subscription.progress_cache.max_capacity).toBe(10000);
18
+ expect(config.infra.api.subscription.progress_cache.time_to_live).toBe("5s");
19
+ }
20
+
21
+ describe("indexer 4.4.0-rc.1 configuration", () => {
22
+ test("accepts exactly a hex-encoded 32-byte application secret", () => {
23
+ expect(isValidIndexerSecret("ab".repeat(32))).toBe(true);
24
+ expect(isValidIndexerSecret("mysecret")).toBe(false);
25
+ expect(isValidIndexerSecret("ab".repeat(31))).toBe(false);
26
+ expect(isValidIndexerSecret("zz".repeat(32))).toBe(false);
27
+ });
28
+
29
+ test("the packaged config contains all required rc.1 subscription fields", () => {
30
+ const config = yaml.load(
31
+ fs.readFileSync(
32
+ path.join(__dirname, "indexer-standalone", "config.yaml"),
33
+ "utf8",
34
+ ),
35
+ );
36
+ expectRc1Schema(config);
37
+ });
38
+
39
+ test("a generated config uses the rc.1 schema and caller endpoints", () => {
40
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "midnight-indexer-config-"));
41
+ const configPath = path.join(dir, "config.yaml");
42
+ try {
43
+ ensureConfigExists(configPath, {
44
+ LEDGER_NETWORK_ID: "Undeployed",
45
+ SUBSTRATE_NODE_WS_URL: "ws://127.0.0.1:15501",
46
+ APP__INFRA__STORAGE__CNN_URL: "/tmp/indexer.sqlite",
47
+ APP__INFRA__LEDGER_DB__CNN_URL: "/tmp/ledger.sqlite",
48
+ APP__INFRA__API__PORT: "15502",
49
+ });
50
+ const config = yaml.load(fs.readFileSync(configPath, "utf8"));
51
+ expectRc1Schema(config);
52
+ expect(config.application.network_id).toBe("undeployed");
53
+ expect(config.infra.node.url).toBe("ws://127.0.0.1:15501");
54
+ expect(config.infra.api.port).toBe(15502);
55
+ } finally {
56
+ fs.rmSync(dir, { force: true, recursive: true });
57
+ }
58
+ });
59
+ });