npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@midnight-ntwrk/compact-js

v3.0.0

Published

Typescript-based execution environment for smart contracts compiled with the Compact language

Readme

Compact.js

Introduction

Compact.js provides a Typescript-based execution environment for smart contracts compiled with the Compact language. When a Compact smart contract is compiled with compactc, part of the output includes:

  1. A JavaScript file.
  2. A TypeScript declaration file.

The JavaScript file contains:

  • The execution logic for each circuit in the source contract,
  • Logic for constructing the contract’s initial state,
  • Utilities for converting on-chain contract state into a JavaScript representation.

Compact.js uses this file at run time to execute the circuits. The circuit execution results are then used by higher level tools and frameworks (such as Midnight.js) in order to create and submit transactions to the Midnight blockchain. At compile time, the types and utilities of Compact.js use the TypeScript declaration file and the definitions it contains, to map types that make working with the contract and its circuits more convenient, and TypeScript idiomatic.

[!NOTE]
The term runtime is often used to describe the JavaScript executable for a contract. This is distinct from the package @midnight-ntwrk/compact-runtime, which provides the utilities that each of these JavaScript executables use.

Ledger eras

The version suffix on an era-pinned entry names the ledger era it targets, not this package's own version: @midnight-ntwrk/compact-js/v9 (and /v9/effect) targets ledger 9, and /v8 (with /v8/effect) targets ledger 8.

The bound era is inspectable at run time via Ledger.era, from any entry.

What each entry gives you

| Entry | Ledger era | compact-runtime | Surface | | --- | --- | --- | --- | | . / ./effect | the build's bound era (ledger 9 today) | 0.21 | Full | | /v9 / /v9/effect | ledger 9 | 0.21 | Full | | /v8 / /v8/effect | ledger 8 | 0.16 | Ledger and CompactRuntime seams only |

/v8/effect binds ledger 8 directly rather than following the package's bound era, so the two era facades can be live in one process.

[!NOTE] /v9 and the unsuffixed entries still resolve the package's bound era. They agree today because that era is ledger 9; when it advances, /v9 must be repointed at a pinned ledger 9 binding the way /v8 is pinned now.

Running both eras in one process

This works, and it is mostly not a compact-js concern — which is the important thing to understand before designing around it.

Every contract compactc emits begins with the same two lines, differing only in the version:

import * as __compactRuntime from '@midnight-ntwrk/compact-runtime';
__compactRuntime.checkRuntimeVersion('0.16.0');

That is a bare specifier in generated code, and checkRuntimeVersion refuses any other minor version. So a contract's era is decided by how that specifier resolves where the contract file lives — not by which compact-js entry your application imported. Importing /v8/effect cannot change it, because an entry point has no reach into generated code's imports.

The pattern, therefore: give each era's contracts a resolution scope whose @midnight-ntwrk/compact-runtime is that era's line. Any of these does it:

  • a nested node_modules beside that era's artifacts (Node resolves bare specifiers by walking up from the importing file);
  • a bundler alias scoped to that directory;
  • a separate workspace or package per era, each pinning its own runtime.

Pin @midnight-ntwrk/compact-js the same way in that scope if you want the full executable surface for that era, rather than just the seams /v8/effect gives you.

Two things are easy to get wrong:

  • Copy the artifacts into the scope; do not symlink them. Node resolves a module to its real path before resolving its imports, so a symlinked artifact resolves its runtime from the original location and fails the version check.
  • The scope needs its own package.json with "type": "module", or the emitted ESM is parsed as CommonJS.

A scope is additive — it redirects only what resolves from inside it, leaving your application's own runtime untouched. test/effect/DualEraResolution.test.ts builds one and executes an era 8 and an era 9 circuit in the same process; read it as the executable version of this section.

Ledger 8 limitations

Two kinds, worth keeping apart.

Era-impossible — these will never exist on ledger 8. Contract events and cross-contract calls. onchain-runtime-v3's log payload carries no emitting-contract address or versioning and nothing accumulates them, and there is no crossContractCall at all. createExecutionContext rejects a cross-contract state provider rather than ignoring it.

Scope-pinned rather than entry-pinned. ContractExecutable, CompiledContract and the configuration services are not exported from /v8/effect. They resolve the Ledger and CompactRuntime facades by module path, so they follow the package's bound era, and exporting them under /v8 would hand back ledger-9-bound objects from a path named v8.

This is deliberate rather than a gap. Executing a ledger 8 contract already requires a ledger 8 resolution scope — see the section above, it is forced by the generated artifact — and inside that scope compact-js is pinned to ledger 8 too, so the executable is era-correct with no extra machinery. /v8/effect covers what is useful without a dedicated scope: decoding and converting ledger 8 state alongside ledger 9, in one process, which is what cross-era history reads need.

Ledger 8 execution itself is verified: the suite compiles counter.compact with compactc 0.31.1 and runs a circuit on the 0.16 line.

Contract log events

Contracts emit typed log events via the Compact emit expression. Each circuit result carries the raw events for the whole call tree on result.events, each tagged with its emitting contract's address.

  • ContractLog decodes raw events into typed, discriminated ContractEvents. Decoding never throws: an oversized, malformed, or dropped payload degrades gracefully (degraded: true) rather than failing the batch.
  • ContractEventStore is an in-process accumulator over decoded events. It assigns a monotonic id on append, supports query with a MIP-aligned filter (contract address, event type, indexed-field hex prefixes, resume cursor), and a live, resumable subscribe stream that replays matching history then tails new events.
import { ContractEventStore, ContractLog } from '@midnight-ntwrk/compact-js/effect';
import { Effect, Stream } from 'effect';

const program = Effect.gen(function* () {
  const store = yield* ContractEventStore.ContractEventStore;

  // Decode a circuit result's raw events and accumulate them.
  const result = yield* contract.circuit(circuitId, ctx, ...args);
  yield* store.append(ContractLog.decodeAll(result.events));

  // Query accumulated events with a MIP-aligned filter.
  const mints = yield* store.query({ eventType: 'unshielded-mint' });

  // Or subscribe to a live, resumable feed (replay from a cursor, then tail).
  yield* store
    .subscribe({ eventType: 'unshielded-mint', fromId: 1n })
    .pipe(Stream.runForEach((event) => Effect.log(event.id)));

  return mints;
}).pipe(Effect.provide(ContractEventStore.layer));