← BACK
feature image
Contents
About Victor Sint Nicolaas, Protocol Engineer - Provable

Victor Sint Nicolaas is Protocol Engineer at Provable. He loves to further the science of cryptography and mechanism design, and aims to bring them into the real world. Previously, he worked on securing taxation and identity software.

Announcing Aleo Stack v4.6.0

The Aleo maintainers are excited to announce a new version of the Aleo Stack. This update includes changes to the node implementation snarkOS and the high-level programming language Leo - as well as their supporting libraries.

This release is one of the largest and most significant upgrades to Aleo since the launch of Mainnet. It introduces three major capabilities:

  • Leo v4.0.0: a complete overhaul of the Leo language, unifying five function-like constructs into a single fn keyword with explicit execution context modifiers.

  • Interfaces and Dynamic Dispatch: compile-time program contracts and runtime function resolution, enabling standardized interfaces, pluggable architectures, and open composability.

  • Leo Libraries: Reusable packages of types, constants, and functions that are shared across programs and fully inlined at compile time

  • Onchain SNARK Verification: new snark.verify and snark.verify.batch opcodes that allow programs to verify Varuna proofs directly onchain.

If you want to try it out, you can build from source today or download the mainnet release from Github March 30th. Find the current release schedule at:https://aleo.org/roadmap.

Consensus Upgrade: V14

The release schedule and upgrade block heights for v4.6.0 can be found here: https://roadmap.aleo.org/

ATTENTION: Validators and clients that do not upgrade in time will be at risk of forking and require manual intervention to recover.

Leo v4.0.0

Leo v4.0.0 is a ground-up rethinking of how Leo programs are written. The previous version of Leo required developers to choose between five distinct function-like constructs — transition, function, inline, async transition, and async function — each with different semantics, calling restrictions, and execution contexts. This release replaces all of them with a single fn keyword.

Unified fn Syntax

All functions now use fn. Where a function executes depends on two things: whether it is inside a program block (making it an entry point), and whether it uses the final modifier (marking it for onchain execution during finalization).

Previous Syntax

New Syntax

Location

transition

fn

Inside program block

function

fn

Outside program block

inline

fn (compiler decides)

Outside program block

async transition

fn returning Final

Inside program block

async function

final fn or final { } block

Outside program block

Future

Final

Type name

.await()

.run()

Execution operator

Before (Leo v3.x):

program token.aleo {     mapping balances: address => u64;     async transition transfer(to: address, amount: u64) -> Future {         return finalize_transfer(to, amount);     }     async function finalize_transfer(to: address, amount: u64) {         let current: u64 = balances.get_or_use(to, 0u64);         balances.set(to, current + amount);     } }

After (Leo v4.0.0):

final fn update_balance(to: address, amount: u64) {     let current: u64 = balances.get_or_use(to, 0u64);     balances.set(to, current + amount); }
program token.aleo {     mapping balances: address => u64;     fn transfer(to: address, amount: u64) -> Final {         return final {             update_balance(to, amount);         };     } }

The program block now clearly defines the program's public interface — only entry points, record declarations, and storage live inside it. Helper functions and type declarations are placed outside. The final keyword explicitly marks functions and blocks that execute on-chain during finalization, replacing the overloaded async terminology. The result is that the distinction between private ZK proof execution and public onchain finalization is visible at a glance.

Automatic Optimization

The compiler now makes inlining decisions automatically. Functions called only once are auto-inlined, and functions never called are eliminated. Developers no longer need to choose between function and inline — the compiler handles it. A @no_inline annotation is available for cases where you want to prevent inlining of a specific function.

Migration

Only the new syntax will be supported going forward from this release. Programs written with transition, function, inline, and async will continue to compile identically. We recommend migrating to the new syntax for all new code. Deprecation warnings for the old syntax will be introduced in a future release.

For detailed migration guidance, refer to the Leo documentation.

Interfaces

Interfaces introduce compile-time contracts for Leo programs. An interface declares what a program must provide — entry point function signatures, record types, and mappings — and the compiler verifies that any program claiming to implement the interface actually does.

interface ARC20 {     record Token {         owner: address,         balance: u64,         ..     }     fn transfer(input: Token, to: address, amount: u64) -> Token; } program my_token.aleo : ARC20 {     record Token {         owner: address,         balance: u64,         memo: field,     }     fn transfer(input: Token, to: address, amount: u64) -> Token {         // ...     } }

The Token annotation here is a compile-time obligation. If any required member is missing or has the wrong signature, the compiler rejects the program.

Composition and Inheritance

Interfaces compose using the + operator and support inheritance:

interface Transfer {     record Token;     fn transfer(input: Token, to: address, amount: u64) -> Token; } interface Balances {     mapping balances: address => u64; } interface ARC20 : Transfer + Balances {} program my_token.aleo : ARC20 {     // Must implement everything from Transfer and Balances }

Record Constraints

Interfaces can require that a record has certain fields without fully specifying its structure. The .. syntax means "these fields are required; the implementor may add more":

interface ARC20 {     record Token {         owner: address,         balance: u64,         ..     } }

This allows programs to extend the record with additional fields (like a memo or created_at timestamp) while still satisfying the interface contract.

Dynamic Dispatch

Until now, every cross-program call in Leo was static — the target program was hardcoded in source and baked into the circuit. To support a second token, you wrote a second function. To support an arbitrary token, you redeployed or upgraded your program.

fn route_transfer_static(to: address, amount: u64) { return token_a.aleo::transfer(to, amount); // Static }

Dynamic dispatch changes this. A single function can now route to any program that implements a known interface, with the target resolved at runtime.

// Dynamic: any program that implements TokenStandard can be called fn transfer_dynamic(token_program:identifier, to:address, amount:u64) { return TokenStandard@(token_program)::transfer_public(to, amount); }

Dynamic Records

Dynamic dispatch solves the problem for program logic. Data has the same rigidity problem — if your function accepts a Token record, it must know the record's exact layout at compile time. The new dyn record type removes this limitation. Programs can accept, inspect, and forward records whose structure is determined at runtime:

fn route_private_transfer(     token_program: field,     input: dyn record,     to: address,     amount: u64 ) -> dyn record { }

Field access on dynamic records is verified by Merkle proof inside the circuit. If a field doesn't exist or has the wrong type, the proof fails.

Security Model

Dynamic dispatch is opt-in and interface-constrained. Static dispatch remains the default. When a program makes a dynamic call, translation proofs automatically verify that records passed across call boundaries are consistent between their static and dynamic representations. The target program, network, and function name are publicly witnessed — private variables influencing the call target are implicitly revealed.

Leo Libraries

Leo now supports libraries — reusable packages of types, constants, and functions that are shared across programs and fully inlined at compile time. Libraries have no transitions, no mappings, and no onchain footprint. Every struct, constant, and function call is resolved and inlined by the compiler before bytecode is emitted.

Creating a Library

A library lives in its own package with a lib.leo entry point (created with leo new --lib). It can define constants, structs (including generic ones), and plain fn functions:

// lib.leo  (package: math) const PRECISION: u32 = 1000u32; struct Vec2 {     x: u32,     y: u32, } fn dot(a: Vec2, b: Vec2) -> u32 {     return a.x * b.x + a.y * b.y; } fn scale::[N: u32](v: Vec2) -> Vec2 {     return Vec2 { x: v.x * N, y: v.y * N }; }

Declaring the Dependency

Add the library to your program's program.json manifest under "dependencies":

{   "program": "myapp.aleo",   "version": "0.1.0",   "description": "",   "license": "MIT",   "dependencies": [     {       "name": "math",       "location": "local",       "path": "../math"     }   ] }

Using the Library

All library items are accessed under the library name as a namespace:

program physics.aleo {     fn kinetic_energy(vx: u32, vy: u32, mass: u32) -> u32 {         let v: math::Vec2 = math::Vec2 { x: vx, y: vy };         let v3: math::Vec2 = math::scale::[3u32](v);  // const arg resolved at compile time         return math::dot(v3, v3) * mass / math::PRECISION;     }     @noupgrade     constructor() {} }

The compiler monomorphizes scale::[3u32] into a concrete function, folds PRECISION to 1000u32, and inlines everything. The resulting bytecode contains no trace of math.

Key Properties

Because libraries are inlined rather than deployed, there are no cross-program call overhead, no ABI boundaries to cross, and generic parameters (array sizes, loop bounds, etc.) are all resolved at compile time — making them the natural home for shared utilities, data structures, and constants.

Libraries

Programs

Compiled to bytecode

No — fully inlined

Yes

Structs (incl. const generic)

Constants

Functions (incl. Const generic)

fn only

All variants

Mappings / storage

Deployed onchain

No

Yes

Onchain SNARK Verification

Aleo Stack v4.6.0 introduces snark.verify and snark.verify.batch; new opcodes that verify Varuna proofs directly onchain within the finalize scope of a program. This implements ARC-0008.

In Leo, these are available as:

program verifier.aleo {     fn verify_proof(         vk: [u8; 1024],         varuna_version: u8,         inputs: [field; 16],         proof: [u8; 512]     ) -> Final {         return final {             let valid: bool = Snark::verify(vk, varuna_version, inputs, proof);             assert(valid);         };     } }

snark.verify.batch supports batch verification of multiple proofs, with compile-time enforcement of snarkVM limits (max 32 circuits, max 128 total instances).

To accommodate the larger data sizes required for verifying keys and proofs, the maximum array size has been increased from 512 to 2,048 elements.

This feature enables proof composition, cross-chain verification, and trustless bridging — any external computation that produces a Varuna proof can now be verified natively by an on-chain Aleo program.

Improvements for Developers

Leo

leo fmt — Code Formatter: Leo now ships with a built-in code formatter. Run leo fmt to format all .leo files in a project, or leo fmt --check for CI integration. The formatter wraps lines at 100 characters and is built on a new lossless Rowan-based parser that preserves comments and whitespace.

leo abi — ABI Generation: A new leo abi command generates a JSON ABI from compiled .aleo files. The ABI includes struct and record definitions, mapping declarations, and transition signatures with input/output types and modes. leo build now automatically writes an abi.json alongside main.aleo.

Faster Testing: leo test now skips proof generation by default, dramatically speeding up test cycles. Opt in to full proof generation with leo test --prove.

Group Generator Access: Two new built-ins — Aleo::generator() and Aleo::generator_powers() — expose the network's group generator point to programs. Aleo::generator() returns the generator as a group, and Aleo::generator_powers() returns a [group; 251] array of precomputed powers, enabling in-program address derivation and other elliptic curve operations.

External Structs: Programs can now reference structs defined in imported programs. Two structs with the same name and identical member structure are considered equivalent, resolving a common pain point when working with multi-program deployments.

Increased Program Limits: Maximum program size has been increased from 100 KB to 512 KB. Maximum transaction size is now 540 KB. Maximum writes per finalize scope increased from 16 to 32.

VK Migration (Amendment Deployments): A new deployment type allows updating a program's verifying keys without changing the program edition, owner, or re-running the constructor. This is essential for adding dynamic dispatch support to existing deployed programs. New REST endpoints support querying amendment counts and transaction history.

Structured Error Messages: When an instruction fails, error messages now include the failing instruction index and a formatted instruction string, producing stack-trace-like output for nested calls.

SDK

Dynamic Dispatch: The DynamicRecord and DynamicFuture types have been added to support dynamic dispatch ,

Utilities: A stringToField()method has been added for converting program names to field elements

Privacy and Security: Proving requests for the Delegated Proving Service can now be encrypted.  A KeyStore abstraction has been added for persistent key storage across environments.  Zeroizing destructors have been added that scrub private key material from WASM memory.  MPC (multi-party computation) support has been added for constructing execution requests. 

WASM Memory: Maximum WASM memory has been raised to ~4 GB. 

API Endpoints: The API endpoint has migrated from api.explorer.provable.com/v1 to api.provable.com/v2.

Improvements for Node Operators

Automated Ledger Checkpoints: A new --auto_db_checkpoints=<path> flag enables rolling RocksDB checkpoint-based ledger backups every 1,000 blocks, maintaining up to 5 rolling snapshots.

Filesystem Reorganization: Node-specific data (peer lists, proposal cache) now lives in a dedicated node-data directory, separate from the ledger. Use --node-data for custom paths and --auto-migrate-node-data for automatic migration.

Dynamic Validator Whitelist: Nodes now maintain a file listing all currently connected validators, updated with every heartbeat. The format is IP-port pairs for easy scripting and firewall integration.

New Metrics: Build info (version, git commit, branch) is now exposed as Prometheus gauge metrics. New gauges track total connected validator stake and the combined stake of validators running the same snarkOS SHA, useful during rolling upgrades.

Network Stability: TCP socket configuration has been hardened, connection timeouts relaxed (handshake increased from 1s to 3s, high-level from 3s to 5s), and the BFT communication layer refactored from channel-based to direct callbacks, reducing async overhead and improving error propagation. CDN sync performance is improved through unchecked deserialization and threadpool reuse.

New REST Endpoints: New routes for historical staking rewards, connected validator queries (/{network}/connections/bft/…), and amendment deployment metadata (/program/{programID}/amendment_count).

Looking Ahead

Interfaces, dynamic dispatch, and dynamic records provide the language-level machinery for standardized program contracts, runtime polymorphism, and open composability on Aleo — with privacy guarantees the EVM cannot provide and interface enforcement that extends beyond function signatures to records and mappings.

The first major consumer of these features is already in progress: ARC-20, Aleo's interoperable token standard. More on that soon.

Closing Notes

The full changelogs for the referenced releases can be found here:

If you want to try it out, you can build from source today or download the mainnet release from Github March 30th. Find the current release schedule at:https://aleo.org/roadmap.

Please report any issues you might come across!

Contributors

A big thank you to all the contributors on Github to this snarkOS, snarkVM, Leo, and SDK release!

@AleoAlexander

@Antonio95

@aripiprazole

@awatts73 

@d0cd 

@damons

@dgrkotsonis

@eranrund

@iamalwaysuncomfortable

@IGI-111 

@JoshuaBatty

@kaimast 

@ljedrz

@marshacb

@meddle0x53

@mitchmindtree

@mohammadfawaz 

@raychu86 

@Roee-87

@shaaibu7 

@tcrypt25519

@tenequm

@vicsn

About Victor Sint Nicolaas, Protocol Engineer - Provable

Victor Sint Nicolaas is Protocol Engineer at Provable. He loves to further the science of cryptography and mechanism design, and aims to bring them into the real world. Previously, he worked on securing taxation and identity software.