@gzeoneth/gov-tracker 0.2.1-alpha.perf-sendroot.fa5e981 → 0.2.1-beta.03fa52b

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.
Files changed (192) hide show
  1. package/README.md +126 -80
  2. package/dist/abis.d.ts +3 -0
  3. package/dist/abis.d.ts.map +1 -1
  4. package/dist/abis.js +30 -2
  5. package/dist/abis.js.map +1 -1
  6. package/dist/calldata/index.d.ts +1 -1
  7. package/dist/calldata/index.d.ts.map +1 -1
  8. package/dist/calldata/index.js.map +1 -1
  9. package/dist/calldata/parameter-decoder.d.ts.map +1 -1
  10. package/dist/calldata/parameter-decoder.js +8 -1
  11. package/dist/calldata/parameter-decoder.js.map +1 -1
  12. package/dist/calldata/signature-lookup.d.ts +17 -2
  13. package/dist/calldata/signature-lookup.d.ts.map +1 -1
  14. package/dist/calldata/signature-lookup.js +20 -2
  15. package/dist/calldata/signature-lookup.js.map +1 -1
  16. package/dist/cli/cli.js +265 -27
  17. package/dist/cli/cli.js.map +1 -1
  18. package/dist/cli/lib/cli.d.ts +50 -2
  19. package/dist/cli/lib/cli.d.ts.map +1 -1
  20. package/dist/cli/lib/cli.js +378 -66
  21. package/dist/cli/lib/cli.js.map +1 -1
  22. package/dist/cli/lib/json-state.d.ts +23 -0
  23. package/dist/cli/lib/json-state.d.ts.map +1 -1
  24. package/dist/cli/lib/json-state.js +51 -4
  25. package/dist/cli/lib/json-state.js.map +1 -1
  26. package/dist/constants.d.ts +39 -1
  27. package/dist/constants.d.ts.map +1 -1
  28. package/dist/constants.js +47 -2
  29. package/dist/constants.js.map +1 -1
  30. package/dist/data/bundled-cache.json +16408 -2561
  31. package/dist/deduplication.d.ts +132 -0
  32. package/dist/deduplication.d.ts.map +1 -0
  33. package/dist/deduplication.js +270 -0
  34. package/dist/deduplication.js.map +1 -0
  35. package/dist/discovery/governor-discovery.d.ts.map +1 -1
  36. package/dist/discovery/governor-discovery.js +52 -36
  37. package/dist/discovery/governor-discovery.js.map +1 -1
  38. package/dist/discovery/timelock-discovery.d.ts +15 -6
  39. package/dist/discovery/timelock-discovery.d.ts.map +1 -1
  40. package/dist/discovery/timelock-discovery.js +27 -11
  41. package/dist/discovery/timelock-discovery.js.map +1 -1
  42. package/dist/election/contracts.d.ts +8 -0
  43. package/dist/election/contracts.d.ts.map +1 -0
  44. package/dist/election/contracts.js +28 -0
  45. package/dist/election/contracts.js.map +1 -0
  46. package/dist/election/details.d.ts +15 -0
  47. package/dist/election/details.d.ts.map +1 -0
  48. package/dist/election/details.js +157 -0
  49. package/dist/election/details.js.map +1 -0
  50. package/dist/election/index.d.ts +11 -0
  51. package/dist/election/index.d.ts.map +1 -0
  52. package/dist/election/index.js +48 -0
  53. package/dist/election/index.js.map +1 -0
  54. package/dist/election/params.d.ts +13 -0
  55. package/dist/election/params.d.ts.map +1 -0
  56. package/dist/election/params.js +93 -0
  57. package/dist/election/params.js.map +1 -0
  58. package/dist/election/participants.d.ts +6 -0
  59. package/dist/election/participants.d.ts.map +1 -0
  60. package/dist/election/participants.js +104 -0
  61. package/dist/election/participants.js.map +1 -0
  62. package/dist/election/prepare.d.ts +10 -0
  63. package/dist/election/prepare.d.ts.map +1 -0
  64. package/dist/election/prepare.js +52 -0
  65. package/dist/election/prepare.js.map +1 -0
  66. package/dist/election/proposal-ids.d.ts +18 -0
  67. package/dist/election/proposal-ids.d.ts.map +1 -0
  68. package/dist/election/proposal-ids.js +77 -0
  69. package/dist/election/proposal-ids.js.map +1 -0
  70. package/dist/election/status.d.ts +15 -0
  71. package/dist/election/status.d.ts.map +1 -0
  72. package/dist/election/status.js +102 -0
  73. package/dist/election/status.js.map +1 -0
  74. package/dist/election/tracking.d.ts +28 -0
  75. package/dist/election/tracking.d.ts.map +1 -0
  76. package/dist/election/tracking.js +412 -0
  77. package/dist/election/tracking.js.map +1 -0
  78. package/dist/index.d.ts +30 -7
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +97 -9
  81. package/dist/index.js.map +1 -1
  82. package/dist/stages/builder.d.ts +4 -0
  83. package/dist/stages/builder.d.ts.map +1 -1
  84. package/dist/stages/builder.js +7 -0
  85. package/dist/stages/builder.js.map +1 -1
  86. package/dist/stages/l2-to-l1-message.d.ts +3 -1
  87. package/dist/stages/l2-to-l1-message.d.ts.map +1 -1
  88. package/dist/stages/l2-to-l1-message.js +6 -6
  89. package/dist/stages/l2-to-l1-message.js.map +1 -1
  90. package/dist/stages/proposal-created.d.ts +1 -0
  91. package/dist/stages/proposal-created.d.ts.map +1 -1
  92. package/dist/stages/proposal-created.js +1 -0
  93. package/dist/stages/proposal-created.js.map +1 -1
  94. package/dist/stages/proposal-queued.d.ts +1 -0
  95. package/dist/stages/proposal-queued.d.ts.map +1 -1
  96. package/dist/stages/proposal-queued.js +3 -1
  97. package/dist/stages/proposal-queued.js.map +1 -1
  98. package/dist/stages/retryables.js +2 -2
  99. package/dist/stages/retryables.js.map +1 -1
  100. package/dist/stages/timelock.d.ts +3 -1
  101. package/dist/stages/timelock.d.ts.map +1 -1
  102. package/dist/stages/timelock.js +10 -4
  103. package/dist/stages/timelock.js.map +1 -1
  104. package/dist/stages/utils.d.ts +7 -8
  105. package/dist/stages/utils.d.ts.map +1 -1
  106. package/dist/stages/utils.js +40 -27
  107. package/dist/stages/utils.js.map +1 -1
  108. package/dist/stages/voting.d.ts.map +1 -1
  109. package/dist/stages/voting.js +5 -4
  110. package/dist/stages/voting.js.map +1 -1
  111. package/dist/tracker/cache.d.ts +10 -6
  112. package/dist/tracker/cache.d.ts.map +1 -1
  113. package/dist/tracker/cache.js +39 -15
  114. package/dist/tracker/cache.js.map +1 -1
  115. package/dist/tracker/checkpoint-helpers.d.ts +81 -0
  116. package/dist/tracker/checkpoint-helpers.d.ts.map +1 -0
  117. package/dist/tracker/checkpoint-helpers.js +201 -0
  118. package/dist/tracker/checkpoint-helpers.js.map +1 -0
  119. package/dist/tracker/discovery.d.ts +40 -9
  120. package/dist/tracker/discovery.d.ts.map +1 -1
  121. package/dist/tracker/discovery.js +152 -15
  122. package/dist/tracker/discovery.js.map +1 -1
  123. package/dist/tracker/pipeline.d.ts.map +1 -1
  124. package/dist/tracker/pipeline.js +26 -11
  125. package/dist/tracker/pipeline.js.map +1 -1
  126. package/dist/tracker/query.d.ts +1 -0
  127. package/dist/tracker/query.d.ts.map +1 -1
  128. package/dist/tracker/query.js +14 -61
  129. package/dist/tracker/query.js.map +1 -1
  130. package/dist/tracker/state.d.ts +0 -10
  131. package/dist/tracker/state.d.ts.map +1 -1
  132. package/dist/tracker/state.js +1 -28
  133. package/dist/tracker/state.js.map +1 -1
  134. package/dist/tracker.d.ts +100 -8
  135. package/dist/tracker.d.ts.map +1 -1
  136. package/dist/tracker.js +405 -42
  137. package/dist/tracker.js.map +1 -1
  138. package/dist/types/config.d.ts +49 -0
  139. package/dist/types/config.d.ts.map +1 -1
  140. package/dist/types/core.d.ts +4 -2
  141. package/dist/types/core.d.ts.map +1 -1
  142. package/dist/types/election.d.ts +143 -0
  143. package/dist/types/election.d.ts.map +1 -1
  144. package/dist/types/index.d.ts +5 -7
  145. package/dist/types/index.d.ts.map +1 -1
  146. package/dist/types/index.js +2 -3
  147. package/dist/types/index.js.map +1 -1
  148. package/dist/types/stages.d.ts +70 -1
  149. package/dist/types/stages.d.ts.map +1 -1
  150. package/dist/types/stages.js.map +1 -1
  151. package/dist/types/tracking.d.ts +34 -4
  152. package/dist/types/tracking.d.ts.map +1 -1
  153. package/dist/utils/block-cache.d.ts +50 -0
  154. package/dist/utils/block-cache.d.ts.map +1 -0
  155. package/dist/utils/block-cache.js +80 -0
  156. package/dist/utils/block-cache.js.map +1 -0
  157. package/dist/utils/formatters.d.ts +91 -0
  158. package/dist/utils/formatters.d.ts.map +1 -0
  159. package/dist/utils/formatters.js +327 -0
  160. package/dist/utils/formatters.js.map +1 -0
  161. package/dist/utils/multicall.d.ts +52 -0
  162. package/dist/utils/multicall.d.ts.map +1 -0
  163. package/dist/utils/multicall.js +75 -0
  164. package/dist/utils/multicall.js.map +1 -0
  165. package/dist/utils/rpc-utils.d.ts +7 -1
  166. package/dist/utils/rpc-utils.d.ts.map +1 -1
  167. package/dist/utils/rpc-utils.js +42 -29
  168. package/dist/utils/rpc-utils.js.map +1 -1
  169. package/dist/utils/salt-computation.d.ts.map +1 -1
  170. package/dist/utils/salt-computation.js +33 -7
  171. package/dist/utils/salt-computation.js.map +1 -1
  172. package/dist/utils/sanitize.d.ts +28 -0
  173. package/dist/utils/sanitize.d.ts.map +1 -0
  174. package/dist/utils/sanitize.js +55 -0
  175. package/dist/utils/sanitize.js.map +1 -0
  176. package/dist/utils/stage-metadata.d.ts +0 -20
  177. package/dist/utils/stage-metadata.d.ts.map +1 -1
  178. package/dist/utils/stage-metadata.js +29 -44
  179. package/dist/utils/stage-metadata.js.map +1 -1
  180. package/dist/utils/timing.d.ts +13 -0
  181. package/dist/utils/timing.d.ts.map +1 -1
  182. package/dist/utils/timing.js +37 -1
  183. package/dist/utils/timing.js.map +1 -1
  184. package/package.json +10 -2
  185. package/dist/election.d.ts +0 -172
  186. package/dist/election.d.ts.map +0 -1
  187. package/dist/election.js +0 -467
  188. package/dist/election.js.map +0 -1
  189. package/dist/types/cross-chain.d.ts +0 -24
  190. package/dist/types/cross-chain.d.ts.map +0 -1
  191. package/dist/types/cross-chain.js +0 -6
  192. package/dist/types/cross-chain.js.map +0 -1
@@ -12,8 +12,9 @@
12
12
  * - commander is an optional dependency required for CLI usage
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.loopOptions = exports.gasOptions = exports.chunkingOptions = exports.executionOptions = exports.cacheOptions = exports.verboseOption = exports.rpcOptions = exports.DEFAULT_L2_GAS_SETTINGS = exports.MAX_CONSECUTIVE_ERRORS = exports.DEFAULT_MAX_AGE_DAYS = exports.DEFAULT_BLOCK_LAG = exports.isElectionGovernor = void 0;
15
+ exports.loopOptions = exports.gasOptions = exports.chunkingOptions = exports.executionOptions = exports.cacheOptions = exports.verboseOption = exports.rpcOptions = exports.HEALTH_CHECK_TIMEOUT_MS = exports.DEFAULT_L2_GAS_SETTINGS = exports.MAX_CONSECUTIVE_ERRORS = exports.DEFAULT_MAX_AGE_DAYS = exports.DEFAULT_BLOCK_LAG = exports.isElectionGovernor = void 0;
16
16
  exports.addOptions = addOptions;
17
+ exports.validateCliOptions = validateCliOptions;
17
18
  exports.parseGasSettings = parseGasSettings;
18
19
  exports.parseChunkingConfig = parseChunkingConfig;
19
20
  exports.createProvidersFromOptions = createProvidersFromOptions;
@@ -24,8 +25,12 @@ exports.formatDryRun = formatDryRun;
24
25
  exports.formatMultiplePreparedTransactions = formatMultiplePreparedTransactions;
25
26
  exports.formatTrackingResult = formatTrackingResult;
26
27
  exports.formatCacheStatus = formatCacheStatus;
28
+ exports.formatElectionResult = formatElectionResult;
29
+ exports.displayTrackingResult = displayTrackingResult;
27
30
  exports.isShuttingDown = isShuttingDown;
28
31
  exports.runWithLoop = runWithLoop;
32
+ exports.filterCheckpointsByTargets = filterCheckpointsByTargets;
33
+ exports.calculateFilteredStats = calculateFilteredStats;
29
34
  exports.runMonitorCycle = runMonitorCycle;
30
35
  exports.trackAndPrepare = trackAndPrepare;
31
36
  const commander_1 = require("commander");
@@ -50,6 +55,8 @@ exports.DEFAULT_L2_GAS_SETTINGS = {
50
55
  /** Max priority fee per gas in gwei */
51
56
  maxPriorityFeePerGas: 0,
52
57
  };
58
+ /** Health check timeout in milliseconds */
59
+ exports.HEALTH_CHECK_TIMEOUT_MS = 5000;
53
60
  // ============================================================================
54
61
  // RPC Configuration
55
62
  // ============================================================================
@@ -112,6 +119,59 @@ exports.loopOptions = [
112
119
  function addOptions(cmd, opts) {
113
120
  opts.forEach((o) => cmd.addOption(o));
114
121
  }
122
+ /**
123
+ * Validate CLI options for contradictions and conflicts.
124
+ * Throws an error if conflicting options are detected.
125
+ */
126
+ function validateCliOptions(opts, command) {
127
+ const errors = [];
128
+ // Cache conflicts
129
+ if (opts.cache && opts.noCache) {
130
+ errors.push({
131
+ flags: ["--cache", "--no-cache"],
132
+ message: "--cache and --no-cache are mutually exclusive",
133
+ });
134
+ }
135
+ // --force overrides --start-block (warn, not error)
136
+ if (opts.force && opts.startBlock) {
137
+ errors.push({
138
+ flags: ["--force", "--start-block"],
139
+ message: "--force resets discovery to block 0, overriding --start-block",
140
+ });
141
+ }
142
+ // Track command specific validations
143
+ if (command === "track") {
144
+ // --inspect and --inspect-only are mutually exclusive
145
+ if (opts.inspect && opts.inspectOnly) {
146
+ errors.push({
147
+ flags: ["--inspect", "--inspect-only"],
148
+ message: "--inspect and --inspect-only are mutually exclusive",
149
+ });
150
+ }
151
+ // --inspect-only skips tracking, so execution options don't make sense
152
+ if (opts.inspectOnly) {
153
+ const conflictingFlags = [];
154
+ if (opts.prepare)
155
+ conflictingFlags.push("--prepare");
156
+ if (opts.write)
157
+ conflictingFlags.push("--write");
158
+ if (opts.prepareCompleted)
159
+ conflictingFlags.push("--prepare-completed");
160
+ if (opts.preparePending)
161
+ conflictingFlags.push("--prepare-pending");
162
+ if (conflictingFlags.length > 0) {
163
+ errors.push({
164
+ flags: ["--inspect-only", ...conflictingFlags],
165
+ message: `--inspect-only skips tracking; ${conflictingFlags.join(", ")} require tracking`,
166
+ });
167
+ }
168
+ }
169
+ }
170
+ if (errors.length > 0) {
171
+ const messages = errors.map((e) => ` ${e.flags.join(" + ")}: ${e.message}`);
172
+ throw new Error(`Conflicting options detected:\n${messages.join("\n")}`);
173
+ }
174
+ }
115
175
  /**
116
176
  * Parse gas settings from CLI options
117
177
  */
@@ -140,6 +200,20 @@ function createProvidersFromOptions(opts) {
140
200
  const l2Rpc = opts.l2Rpc || process.env.ARB1_RPC || index_1.DEFAULT_RPC_URLS.ARB_ONE;
141
201
  const l1Rpc = opts.l1Rpc || process.env.ETH_RPC || index_1.DEFAULT_RPC_URLS.ETHEREUM;
142
202
  const novaRpc = opts.novaRpc || process.env.NOVA_RPC || index_1.DEFAULT_RPC_URLS.NOVA;
203
+ // Warn when using default public RPCs (no env var or CLI option provided)
204
+ const warnings = [];
205
+ if (!opts.l1Rpc && !process.env.ETH_RPC) {
206
+ warnings.push("Using public RPC URL for Ethereum (ETH_RPC not set)");
207
+ }
208
+ if (!opts.l2Rpc && !process.env.ARB1_RPC) {
209
+ warnings.push("Using public RPC URL for Arbitrum One (ARB1_RPC not set)");
210
+ }
211
+ if (!opts.novaRpc && !process.env.NOVA_RPC) {
212
+ warnings.push("Using public RPC URL for Nova (NOVA_RPC not set)");
213
+ }
214
+ if (warnings.length > 0) {
215
+ console.warn(`Warning: ${warnings.join("; ")}. Public RPCs may have rate limits.`);
216
+ }
143
217
  // Use StaticJsonRpcProvider to avoid automatic chainId detection on every call
144
218
  return {
145
219
  l2Provider: new ethers_1.ethers.providers.StaticJsonRpcProvider(l2Rpc),
@@ -324,57 +398,114 @@ function formatTrackingResult(result, label) {
324
398
  }
325
399
  return lines.join("\n").concat("\n");
326
400
  }
327
- function formatCacheStatus(checkpoints) {
328
- let proposalTotal = 0, proposalComplete = 0, proposalActive = 0, proposalFailed = 0;
329
- let timelockTotal = 0, timelockComplete = 0, timelockActive = 0, timelockFailed = 0;
330
- let electionTotal = 0, electionComplete = 0;
331
- for (const [, checkpoint] of checkpoints) {
332
- const stages = checkpoint.cachedData?.completedStages ?? [];
333
- const isComplete = stages.length > 0 && (0, index_1.areAllStagesComplete)(stages);
334
- const errorCount = checkpoint.metadata?.errorCount ?? 0;
335
- const isFailed = errorCount >= exports.MAX_CONSECUTIVE_ERRORS;
336
- const input = checkpoint.input;
337
- if (input.type === "governor") {
338
- if ((0, index_1.isElectionGovernor)(input.governorAddress)) {
339
- electionTotal++;
340
- if (isComplete)
341
- electionComplete++;
401
+ function formatCacheStatus(checkpoints, elections) {
402
+ const stats = (0, index_1.computeCacheStats)(checkpoints, elections, exports.MAX_CONSECUTIVE_ERRORS);
403
+ const lines = [
404
+ `Total cached: ${stats.total}`,
405
+ ``,
406
+ `Proposals: ${stats.proposals.total}`,
407
+ ` Complete: ${stats.proposals.complete}`,
408
+ ` Active: ${stats.proposals.active}`,
409
+ ];
410
+ if (stats.proposals.errored > 0)
411
+ lines.push(` Failed: ${stats.proposals.errored}`);
412
+ lines.push(``, `Timelock Ops: ${stats.timelocks.total}`, ` Complete: ${stats.timelocks.complete}`, ` Active: ${stats.timelocks.active}`);
413
+ if (stats.timelocks.errored > 0)
414
+ lines.push(` Failed: ${stats.timelocks.errored}`);
415
+ if (stats.elections.total > 0) {
416
+ const active = stats.elections.total - stats.elections.complete;
417
+ lines.push(``, `Elections: ${stats.elections.total}`, ` Complete: ${stats.elections.complete}`, ` Active: ${active}`);
418
+ }
419
+ return lines.join("\n");
420
+ }
421
+ /**
422
+ * Format an election status for CLI output
423
+ */
424
+ function formatElectionResult(election) {
425
+ const lines = [];
426
+ const cohortName = election.cohort === 0 ? "First" : "Second";
427
+ const phaseName = election.phase.replace(/_/g, " ");
428
+ lines.push(`[Election #${election.electionIndex}]`);
429
+ lines.push(`Phase: ${phaseName}`);
430
+ lines.push(`Cohort: ${cohortName}`);
431
+ lines.push(`Complete: ${election.phase === "COMPLETED"}`);
432
+ lines.push(`Stages: ${election.stages?.length ?? 0}`);
433
+ lines.push("");
434
+ if (election.stages && election.stages.length > 0) {
435
+ for (let i = 0; i < election.stages.length; i++) {
436
+ const stage = election.stages[i];
437
+ const statusStr = stage.status;
438
+ const title = (0, index_1.formatStageTitle)(stage.type);
439
+ const eta = (0, index_1.calculateExpectedEta)(election.stages, i);
440
+ let line = ` ${title}: ${statusStr}`;
441
+ if (eta)
442
+ line += ` | ETA: ${new Date(eta * 1000).toISOString()}`;
443
+ lines.push(line);
444
+ for (const tx of stage.transactions) {
445
+ lines.push(` tx: ${tx.hash}`);
446
+ const url = (0, index_1.getStageTransactionUrl)(tx);
447
+ if (url)
448
+ lines.push(` ${url}`);
342
449
  }
343
- else {
344
- proposalTotal++;
345
- if (isComplete)
346
- proposalComplete++;
347
- else if (isFailed)
348
- proposalFailed++;
349
- else
350
- proposalActive++;
450
+ }
451
+ }
452
+ // Show timelock operation ID if tracked (useful for cross-referencing)
453
+ if (election.timelockOperationId) {
454
+ lines.push(` Timelock Op: ${election.timelockOperationId}`);
455
+ }
456
+ if (election.canProceedToMemberPhase) {
457
+ lines.push("");
458
+ lines.push(" → Ready to trigger member election");
459
+ }
460
+ if (election.canExecuteMember) {
461
+ lines.push("");
462
+ lines.push(" → Ready to execute member election");
463
+ }
464
+ if (election.canCreateElection) {
465
+ lines.push("");
466
+ lines.push(" → Ready to create election");
467
+ }
468
+ lines.push("");
469
+ return lines.join("\n");
470
+ }
471
+ /**
472
+ * Display a tracking result, automatically switching to election display for election proposals.
473
+ * Handles the election auto-switch logic internally, falling back to formatTrackingResult for non-elections.
474
+ *
475
+ * @param result - Tracking result to display
476
+ * @param label - Optional label prefix
477
+ */
478
+ function displayTrackingResult(result, label) {
479
+ if (result.isElection && result.electionStatus) {
480
+ const election = result.electionStatus;
481
+ const cohortName = election.cohort === 0 ? "First" : "Second";
482
+ console.log(`=== Election #${election.electionIndex} ===`);
483
+ console.log(`Phase: ${election.phase}`);
484
+ console.log(`Cohort: ${cohortName} (${election.cohort})`);
485
+ console.log(`Compliant Nominees: ${election.compliantNomineeCount}/${election.targetNomineeCount}`);
486
+ if (election.nomineeProposalId) {
487
+ console.log(`\nElection ID: ${election.nomineeProposalId}`);
488
+ console.log(`\nNominee Phase:`);
489
+ console.log(` State: ${election.nomineeProposalState}`);
490
+ if (election.vettingDeadline) {
491
+ console.log(` Vetting Deadline: block ${election.vettingDeadline}`);
351
492
  }
493
+ console.log(` In Vetting Period: ${election.isInVettingPeriod ? "YES" : "NO"}`);
352
494
  }
353
- else if (input.type === "timelock") {
354
- timelockTotal++;
355
- if (isComplete)
356
- timelockComplete++;
357
- else if (isFailed)
358
- timelockFailed++;
359
- else
360
- timelockActive++;
495
+ if (election.memberProposalId) {
496
+ console.log(`\nMember Phase:`);
497
+ console.log(` State: ${election.memberProposalState}`);
361
498
  }
499
+ if (election.canProceedToMemberPhase) {
500
+ console.log(`\n→ Ready to trigger member election`);
501
+ }
502
+ if (election.canExecuteMember) {
503
+ console.log(`\n→ Ready to execute member election`);
504
+ }
505
+ console.log("");
506
+ return;
362
507
  }
363
- const lines = [
364
- `Total cached: ${checkpoints.size}`,
365
- ``,
366
- `Proposals: ${proposalTotal}`,
367
- ` Complete: ${proposalComplete}`,
368
- ` Active: ${proposalActive}`,
369
- ];
370
- if (proposalFailed > 0)
371
- lines.push(` Failed: ${proposalFailed}`);
372
- lines.push(``, `Timelock Ops: ${timelockTotal}`, ` Complete: ${timelockComplete}`, ` Active: ${timelockActive}`);
373
- if (timelockFailed > 0)
374
- lines.push(` Failed: ${timelockFailed}`);
375
- if (electionTotal > 0)
376
- lines.push(``, `Elections: ${electionTotal} (${electionComplete} complete)`);
377
- return lines.join("\n");
508
+ console.log(formatTrackingResult(result, label));
378
509
  }
379
510
  // ============================================================================
380
511
  // Loop Runner
@@ -423,10 +554,13 @@ async function runWithLoop(cycleFn, options) {
423
554
  }
424
555
  if (options.healthCheckUrl) {
425
556
  try {
426
- await fetch(options.healthCheckUrl, { method: "GET" });
557
+ const controller = new AbortController();
558
+ const timeoutId = setTimeout(() => controller.abort(), exports.HEALTH_CHECK_TIMEOUT_MS);
559
+ await fetch(options.healthCheckUrl, { method: "GET", signal: controller.signal });
560
+ clearTimeout(timeoutId);
427
561
  }
428
562
  catch {
429
- // Silently ignore health check errors
563
+ // Silently ignore health check errors (including timeouts)
430
564
  }
431
565
  }
432
566
  if (running)
@@ -530,27 +664,193 @@ function shortScope(key) {
530
664
  }
531
665
  return key;
532
666
  }
667
+ /**
668
+ * Check if a checkpoint matches the enabled discovery targets.
669
+ * Used to filter incomplete checkpoints to only re-track types that are enabled.
670
+ */
671
+ function checkpointMatchesTargets(checkpoint, targets) {
672
+ if (checkpoint.input.type === "governor") {
673
+ const governorAddress = checkpoint.input.governorAddress;
674
+ // Skip election governors - they're handled separately
675
+ if ((0, index_1.isElectionGovernor)(governorAddress))
676
+ return false;
677
+ // Check if constitutional or non-constitutional governor is enabled
678
+ const isCore = (0, index_1.isConstitutional)(governorAddress);
679
+ return isCore ? !!targets.constitutionalGovernor : !!targets.nonConstitutionalGovernor;
680
+ }
681
+ if (checkpoint.input.type === "timelock") {
682
+ // Timelock operations: check if the corresponding timelock target is enabled
683
+ const timelockAddress = checkpoint.input.timelockAddress;
684
+ const isCore = (0, index_1.isConstitutional)(timelockAddress);
685
+ return isCore ? !!targets.l2ConstitutionalTimelock : !!targets.l2NonConstitutionalTimelock;
686
+ }
687
+ if (checkpoint.input.type === "election") {
688
+ return !!targets.electionNomineeGovernor || !!targets.electionMemberGovernor;
689
+ }
690
+ // Discovery checkpoints don't need re-tracking
691
+ return false;
692
+ }
693
+ /**
694
+ * Filter checkpoints to only include those matching enabled discovery targets.
695
+ * Used for JSON output and stats to ensure we only show tracked types.
696
+ */
697
+ function filterCheckpointsByTargets(checkpoints, targets) {
698
+ const filtered = new Map();
699
+ for (const [key, checkpoint] of checkpoints) {
700
+ if (checkpointMatchesTargets(checkpoint, targets)) {
701
+ filtered.set(key, checkpoint);
702
+ }
703
+ }
704
+ return filtered;
705
+ }
706
+ /**
707
+ * Calculate stats filtered by enabled discovery targets.
708
+ * Only counts checkpoints that match the enabled governors/timelocks.
709
+ */
710
+ function calculateFilteredStats(checkpoints, targets, maxErrorCount = 5) {
711
+ let proposalTotal = 0, proposalComplete = 0, proposalActive = 0, proposalErrored = 0;
712
+ let timelockTotal = 0, timelockComplete = 0, timelockActive = 0, timelockErrored = 0;
713
+ let electionTotal = 0, electionComplete = 0;
714
+ for (const [, checkpoint] of checkpoints) {
715
+ const complete = (0, index_1.isCheckpointComplete)(checkpoint);
716
+ const errored = (0, index_1.isCheckpointErrored)(checkpoint, maxErrorCount);
717
+ const inputType = checkpoint.input.type;
718
+ if (inputType === "governor") {
719
+ const governorAddress = checkpoint.input.governorAddress;
720
+ if ((0, index_1.isElectionGovernor)(governorAddress))
721
+ continue;
722
+ const isCore = (0, index_1.isConstitutional)(governorAddress);
723
+ const matchesTarget = isCore
724
+ ? !!targets.constitutionalGovernor
725
+ : !!targets.nonConstitutionalGovernor;
726
+ if (!matchesTarget)
727
+ continue;
728
+ proposalTotal++;
729
+ if (complete)
730
+ proposalComplete++;
731
+ else if (errored)
732
+ proposalErrored++;
733
+ else
734
+ proposalActive++;
735
+ }
736
+ else if (inputType === "timelock") {
737
+ const timelockAddress = checkpoint.input.timelockAddress;
738
+ const isCore = (0, index_1.isConstitutional)(timelockAddress);
739
+ const matchesTarget = isCore
740
+ ? !!targets.l2ConstitutionalTimelock
741
+ : !!targets.l2NonConstitutionalTimelock;
742
+ if (!matchesTarget)
743
+ continue;
744
+ timelockTotal++;
745
+ if (complete)
746
+ timelockComplete++;
747
+ else if (errored)
748
+ timelockErrored++;
749
+ else
750
+ timelockActive++;
751
+ }
752
+ else if (inputType === "election") {
753
+ // Only count elections if election tracking is enabled
754
+ if (!targets.electionNomineeGovernor && !targets.electionMemberGovernor)
755
+ continue;
756
+ electionTotal++;
757
+ if (complete)
758
+ electionComplete++;
759
+ }
760
+ }
761
+ return {
762
+ total: proposalTotal + timelockTotal + electionTotal,
763
+ proposals: {
764
+ total: proposalTotal,
765
+ complete: proposalComplete,
766
+ active: proposalActive,
767
+ errored: proposalErrored,
768
+ },
769
+ timelocks: {
770
+ total: timelockTotal,
771
+ complete: timelockComplete,
772
+ active: timelockActive,
773
+ errored: timelockErrored,
774
+ },
775
+ elections: { total: electionTotal, complete: electionComplete },
776
+ };
777
+ }
533
778
  async function runMonitorCycle(tracker, providers, options = {}) {
779
+ const result = { tracked: 0, prepared: 0, errors: 0, retracked: 0 };
780
+ // Fast path: elections-only mode skips discovery entirely
781
+ if (options.electionsOnly) {
782
+ const elections = [];
783
+ if (!isShuttingDown()) {
784
+ try {
785
+ // Use tracker's cached method - completed elections use cache (0 RPC calls)
786
+ // Use forceElections to bypass cache when --force is specified
787
+ const allElections = await tracker.trackAllElections({ force: options.forceElections });
788
+ for (const electionStatus of allElections) {
789
+ elections.push(electionStatus);
790
+ // Print each election
791
+ console.log(formatElectionResult(electionStatus));
792
+ }
793
+ }
794
+ catch (err) {
795
+ console.error("Election tracking failed:", err);
796
+ }
797
+ }
798
+ return {
799
+ result,
800
+ proposals: [],
801
+ timelockOps: [],
802
+ watermarks: {},
803
+ elections,
804
+ };
805
+ }
534
806
  const l2Provider = providers.l2Provider;
535
807
  const tipBlock = await l2Provider.getBlockNumber();
536
808
  const blockLag = options.blockLag ?? exports.DEFAULT_BLOCK_LAG;
537
809
  const currentBlock = Math.max(0, tipBlock - blockLag);
538
810
  const concurrency = options.concurrency ?? 1;
539
811
  const limit = (0, concurrency_1.pLimit)(concurrency);
540
- // Optional startBlock override (watermarks are exclusive, so subtract 1)
541
- const startBlockWatermarks = options.startBlock
542
- ? {
812
+ const maxAgeDays = options.maxAgeDays ?? exports.DEFAULT_MAX_AGE_DAYS;
813
+ // Calculate startBlock based on maxAgeDays if not explicitly provided
814
+ // ~7200 blocks per day on Arbitrum (12s block time on L1, ~250ms on L2)
815
+ const BLOCKS_PER_DAY = 86400 / 0.25; // ~345,600 blocks/day on Arbitrum One
816
+ let startBlockWatermarks;
817
+ // Load existing watermarks to determine scan range
818
+ const loadedWatermarks = await tracker.loadWatermarks();
819
+ const hasWatermarks = Object.values(loadedWatermarks.watermarks).some((v) => v !== undefined);
820
+ if (options.startBlock !== undefined) {
821
+ // Explicit startBlock override (watermarks are exclusive, so subtract 1)
822
+ startBlockWatermarks = {
543
823
  constitutionalGovernor: options.startBlock - 1,
544
824
  nonConstitutionalGovernor: options.startBlock - 1,
545
825
  electionNomineeGovernor: options.startBlock - 1,
546
826
  electionMemberGovernor: options.startBlock - 1,
547
827
  l2ConstitutionalTimelock: options.startBlock - 1,
548
828
  l2NonConstitutionalTimelock: options.startBlock - 1,
549
- }
550
- : undefined;
551
- const targets = (0, index_1.buildDefaultTargets)();
829
+ };
830
+ console.log(`Discovery: blocks ${options.startBlock} → ${currentBlock}`);
831
+ }
832
+ else if (!hasWatermarks) {
833
+ // No cache - calculate start block from maxAgeDays
834
+ const defaultStartBlock = Math.max(0, currentBlock - Math.floor(maxAgeDays * BLOCKS_PER_DAY));
835
+ startBlockWatermarks = {
836
+ constitutionalGovernor: defaultStartBlock,
837
+ nonConstitutionalGovernor: defaultStartBlock,
838
+ electionNomineeGovernor: defaultStartBlock,
839
+ electionMemberGovernor: defaultStartBlock,
840
+ l2ConstitutionalTimelock: defaultStartBlock,
841
+ l2NonConstitutionalTimelock: defaultStartBlock,
842
+ };
843
+ console.log(`No cached watermarks. Discovery: blocks ${defaultStartBlock} → ${currentBlock} (~${maxAgeDays} days)`);
844
+ }
845
+ else {
846
+ // Cached watermarks exist - calculate effective range from min watermark
847
+ const watermarkBlocks = Object.values(loadedWatermarks.watermarks).filter((v) => v !== undefined);
848
+ const minWatermark = Math.min(...watermarkBlocks);
849
+ const blockRange = currentBlock - minWatermark;
850
+ console.log(`Discovery: blocks ${minWatermark} → ${currentBlock} (${blockRange.toLocaleString()} blocks)`);
851
+ }
852
+ const targets = options.targets ?? (0, index_1.buildDefaultTargets)();
552
853
  const discoveryResult = await tracker.discoverAll(targets, currentBlock, startBlockWatermarks);
553
- const result = { tracked: 0, prepared: 0, errors: 0, retracked: 0 };
554
854
  const trackedKeys = new Set();
555
855
  const trackedOperationIds = new Set();
556
856
  async function track(key, trackFn) {
@@ -604,7 +904,6 @@ async function runMonitorCycle(tracker, providers, options = {}) {
604
904
  });
605
905
  }
606
906
  // Query incomplete checkpoints first to avoid duplicate tracking
607
- const maxAgeDays = options.maxAgeDays ?? exports.DEFAULT_MAX_AGE_DAYS;
608
907
  const incompleteCheckpoints = await tracker.queryIncompleteCheckpoints({
609
908
  maxAgeDays,
610
909
  maxErrorCount: exports.MAX_CONSECUTIVE_ERRORS,
@@ -635,14 +934,10 @@ async function runMonitorCycle(tracker, providers, options = {}) {
635
934
  },
636
935
  });
637
936
  }
638
- // 1b. Incomplete governor checkpoints to re-track (excluding elections)
639
- const nonElectionCheckpoints = incompleteCheckpoints.filter(({ checkpoint }) => {
640
- if (checkpoint.input.type === "governor")
641
- return !(0, index_1.isElectionGovernor)(checkpoint.input.governorAddress);
642
- return true;
643
- });
644
- result.retracked = nonElectionCheckpoints.length;
645
- for (const { key, checkpoint } of nonElectionCheckpoints) {
937
+ // 1b. Incomplete checkpoints to re-track (filtered by enabled targets)
938
+ const targetMatchingCheckpoints = incompleteCheckpoints.filter(({ checkpoint }) => checkpointMatchesTargets(checkpoint, targets));
939
+ result.retracked = targetMatchingCheckpoints.length;
940
+ for (const { key, checkpoint } of targetMatchingCheckpoints) {
646
941
  if (checkpoint.input.type === "governor") {
647
942
  proposalTasks.push({
648
943
  key,
@@ -680,7 +975,7 @@ async function runMonitorCycle(tracker, providers, options = {}) {
680
975
  });
681
976
  }
682
977
  // 2b. Incomplete timelock checkpoints to re-track
683
- for (const { key, checkpoint } of nonElectionCheckpoints) {
978
+ for (const { key, checkpoint } of targetMatchingCheckpoints) {
684
979
  if (checkpoint.input.type === "timelock") {
685
980
  const operationId = checkpoint.input.operationId;
686
981
  // Skip if already tracked via proposal
@@ -696,11 +991,28 @@ async function runMonitorCycle(tracker, providers, options = {}) {
696
991
  }
697
992
  // Run timelock tasks
698
993
  await Promise.all(timelockTasks.map((task) => limit(() => track(task.key, task.fn))));
994
+ // Phase 3: Track elections (only if election targets are enabled)
995
+ const elections = [];
996
+ const shouldTrackElections = targets.electionNomineeGovernor || targets.electionMemberGovernor;
997
+ if (shouldTrackElections && !isShuttingDown()) {
998
+ try {
999
+ // Use tracker's cached method - completed elections use cache (0 RPC calls)
1000
+ const allElections = await tracker.trackAllElections({ force: options.forceElections });
1001
+ for (const electionStatus of allElections) {
1002
+ elections.push(electionStatus);
1003
+ }
1004
+ }
1005
+ catch (err) {
1006
+ // Election tracking is non-critical, log and continue
1007
+ console.error("Election tracking failed:", err);
1008
+ }
1009
+ }
699
1010
  return {
700
1011
  result,
701
1012
  proposals: discoveryResult.proposals,
702
1013
  timelockOps: discoveryResult.timelockOps,
703
1014
  watermarks: discoveryResult.watermarks,
1015
+ elections,
704
1016
  };
705
1017
  }
706
1018
  // ============================================================================