Compact toolchain 0.28.0 (Compact language 0.20.0)
For the complete documentation index, see llms.txt
Version
- Version: Compact toolchain 0.28.0, Compact language 0.20.0
- Date: 2026-01-28
- Environment: Preview, Preprod
High-level summary
The Compact toolchain version 0.28.0 is being released today. This version compiles Compact language version 0.20.0, which is an updated version of the language.
This is the first version of the toolchain which targets the new "Preproduction Testnet" ("Preprod" for short) and "Preview Testnet" ("Preview" for short) environments of the Midnight Network. Contracts compiled with compiler version 0.28.0 or later will not work with the previous environment (known as "testnet" or "testnet-02"). Similarly, contracts compiled with versions earlier than 0.28.0 will not work with the Preprod or Preview environments.
This release is part of an important milestone for the Midnight Network: the launch of the new Preprod and Preview environments. Please read these release notes carefully to note the breaking changes.
Audience
These release notes are intended for Compact contract developers and for DApp developers who use the Compact runtime.
Summary of updates
The Preprod and Preview environments are using Midnight ledger version 7 (compared to testnet's ledger version 4). This version of the ledger has new support for native unshielded tokens.
- The Compact standard library has new APIs for unshielded tokens and some breaking renaming changes to existing APIs.
- Compact now supports the definition of both structural ("transparent") and nominal ("opaque") type aliases.
- Compact now supports selective module import and renaming.
- The meaning of
Uintranges has changed. - The maximum representable unsigned integer is smaller.
- There are new compiler-enforced bounds on various sizes. This is a breaking change because programs that previously compiled will not compile now. However, those contracts were unlikely to work anyway.
- The standard library type
CurvePointis renamed toNativePoint. It is a nominal type alias for an unexported internal type. - The standard library exports new circuits
NativePointXandNativePointYas accessors forNativePoint. - The generated JavaScript code and the Compact runtime now use ECMAScript modules (ESM) instead Common JS (CJS) modules. This is a breaking change for your DApps but it will likely simplify them for developers who are working purely with ESM.
- Upward casts no longer prevent tuple references and slices from recognizing constant indexes.
- Out-of-range bytes value indexes are detected earlier in the compiler, which means additional errors might be signaled for programs which previously compiled without error.
- The reserved words from TypeScript and JavaScript are now included as reserved words in Compact.
returnis now disallowed in the body offorloops.- The compiler no longer generates ZKIR code and prover and verifier keys for certain circuits that do not need them.
- The formatting of circuit signatures, calls, and anonymous circuits is improved.
- The formatting of
if/else ifstatements is improved. - The formatter line length is configurable, set to 100 columns by default.
- This release has several bug fixes and the usual performance improvements.
New features
Below is a detailed breakdown of new features added in this release.
Unshielded tokens
There are new standard library circuits and ledger kernel operations for working with unshielded tokens.
There are new circuits for minting and transferring unshielded tokens:
mintUnshieldedToken, sendUnshielded, and receiveUnshielded.
There are also new circuits for working with unshielded token balances:
unshieldedBalance, unshieldedBalanceLt, unshieldedBalanceGt, unshieldedBalanceLte, and unshieldedBalanceGte.
The ledger kernel has new operations mintUnshielded, claimUnshieldedCoinSpend, incUnshieldedOutputs, and incUnshieldedInputs.
This is not a breaking change, but it did require us to make a breaking change noted below.
Type aliases
Compact now supports both structural ("transparent") and nominal ("opaque") type aliases.
A structural type alias is the same as in TypeScript.
The declaration type Name = Type defines Name to be an alias for Type.
The types Name and Type are exactly the same type.
For example type U32 = Uint<32> defines U32 to be the equivalent of and interchangeable with Uint<32>.
A nominal type alias introduces a new distinct type whose representation is given by an underlying type.
This feature does not exist in TypeScript.
The declaration new type Name = Type introduces Name and a distinct type whose values are the same as the values of type Type.
Name is neither a subtype nor a supertype of Type.
They are compatible in the sense that:
- values of type
Namecan be used in primitive operations (such as arithmetic) that can use a value of typeType, and - values of type
Namecan be explicitly cast to and from typeType.
For example, type RGB = Vector<3, Uint<8>> defines RGB to be a distinct type.
Values of type RGB can be referenced or sliced just like a vector of type Vector<3, Uint<8>>,
but the cannot, for example, be passed to a function that expects a value of type Vector<3, Uint<8>> without an explicit cast.
When a value of an arithmetic operation receives a value of a nominaly type alias T,
the other operand must also be of type T and the result is cast to type T,
which might cause a type error if the result cannot be represented by T.
Both types of aliases can have generic parameters.
For example, type V3<T> = Vector<3, T> or new type VField<#N> = Vector<N, Field>.
Selective module importing and renaming
You can now selectively import individual elements from Compact modules, and you can individually rename imported elements.
This works like it does in TypeScript and JavaScript's ES modules.
For example:
import { getMatch, putMatch as originalPutMatch } from Matching;
will import getMatch as getMatch and putMatch as originalPutMatch, and it will not import any other elements of Matching.
This can be used to avoid name clashes (by renaming) including possible future ones if new elements are added to a module (by only importing what you use).
Selective import and renaming works with Compact's existing module prefixes:
import { getMatch, putMatch as originalPutMatch } from Matching prefix M_;
will import getMatch as M_getMatch and putMatch as M_originalPutMatch
Note that selective import and renaming works for the standard library too, because CompactStandardLibrary is a module.
The original form of import is still supported, though technically this is a breaking change because from has been added to the set of reserved words.
Improvements
Below is a detailed breakdown of existing features that have been improved in this release.
There are improvements to the formatter
- We have changed the formatter's formatting of multiple-line circuit signatures, calls, and anonymous circuits. Previously, the formatting had a tendency to "run away" to the right because we were overzealous about vertical alignment. Now, there is much less horizontal whitespace in such code.
- The formatter's handling of chained
if/else ifstatements did not match standard practice from C-style languages (which includes TypeScript and JavaScript). We have improved this. - The formatter's line length is now configurable.
You can pass
--line-length Ntocompact format, whereNis the number of columns. It is set to 100 columns by default.
Upward casts do not prevent tuple constant indexing
In the previous release we allowed tuples, vectors, and bytes values to be indexed by expressions that were not numeric literals, as long as those expressions could be determined by the compiler to have a specific constant value.
The compiler analysis that recognized constant-valued expressions did not correctly recognize upcasts of tuple values (a type cast of a tuple to a supertype). We have changed that.
The result is that more programs that do in fact use constant indexing into tuples will typecheck and compile.