Skip to content

feat: store immutables#20

Merged
zkfrov merged 8 commits into
test/jsfrom
feat/store-immutables
Feb 20, 2026
Merged

feat: store immutables#20
zkfrov merged 8 commits into
test/jsfrom
feat/store-immutables

Conversation

@zkfrov
Copy link
Copy Markdown
Collaborator

@zkfrov zkfrov commented Feb 20, 2026

🤖 Linear

Closes AZT-727

Description

  • Introduce persistent immutable storage via PXE CapsuleStore — store_immutables() replaces per-transaction transient capsules with a one-time .simulate() call after deployment
  • Add #[abi(immutables)] layout generation to the #[immutables] macro, enabling TypeScript artifact introspection (getImmutablesLayout, serializeFromLayout)
  • Add production-ready deploySchnorrInitializerlessAccount() to the public API for npm consumers
  • Rename publication options from skipClassPublication/skipInstancePublication to opt-in publishClass/publishInstance (default false)

Changes

Noir (src/nr/)

  • #[immutables] macro: generates Immutables::store(context, capsule_data) — validates poseidon2_hash(capsule_data) == instance.salt then persists to PXE CapsuleStore
  • #[immutables] macro: generates #[abi(immutables)] layout (field names, indices, serialized length) for TypeScript introspection — mirrors #[abi(storage)] from aztec-nr
  • ImmutablesContract / SchnorrInitializerlessAccount: add store_immutables utility wrapper (manual, pending macro auto-generation)

TypeScript (src/ts/)

  • immutables/utils.ts: add getImmutablesLayout(), serializeFromLayout(), artifact layout validation in deployWithImmutables, automatic store_immutables().simulate() call during deployment, return
    capsuleData: Fr[] instead of actualSalt: Fr
  • immutables/utils.ts: rename skipClassPublication/skipInstancePublicationpublishClass/publishInstance (default false)
  • schnorr-initializerless-account/index.ts: add deploySchnorrInitializerlessAccount() (full lifecycle: key derivation, deploy, Account creation), update serializeSigningKey to use serializeFromLayout,
    return capsuleData from computeSchnorrAccountAddress
  • Delete schnorr-initializerless-account/utils.ts: redundant with deploySchnorrInitializerlessAccount in index.ts

Tests

  • All tests updated: actualSaltcapsuleData, registerInitializerlessAccountdeploySchnorrInitializerlessAccount, opt-in publication flags
  • E2E tests use local deployAndRegister helper for TestWallet-specific account registration

Docs

  • README rewritten: persistent store flow, store_immutables wrapper docs, PXE recovery, npm usage examples, capsule data loss warning

@linear
Copy link
Copy Markdown

linear Bot commented Feb 20, 2026


/// Generates the `#[abi(immutables)]` layout for the contract artifact.
///
/// Mirrors the `#[abi(storage)]` pattern from aztec-nr (`aztec/src/macros/storage.nr:96-109`).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

full link to a fixed commit 🙏 (tagging v4.devnet... may be smart so it appears when search and replacing to next version), how can we make sure references to lines are updated? coderabbit?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the time being added the proper fixed commit in #10

Comment on lines +106 to +115
// TODO: This wrapper should be auto-generated by the #[immutables] macro or
// enshrined in aztec-nr. Currently requires a manual wrapper in each contract
// because struct macros cannot emit #[external("utility")] functions (blocked
// by #[aztec]'s check_all_functions_have_context_macro).
/// Persists immutables data to the PXE's CapsuleStore.
/// Called once after deployment via `.simulate()` from TypeScript.
#[external("utility")]
unconstrained fn store_immutables(capsule_data: [Field; 3]) {
Immutables::store(self.context, capsule_data);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's check whether this is intended or oversight, it may be wise to trigger an issue explaining our usecase

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended, I understand that introducing a injecting a macro like #[external("utility")] is not possible from another macro.


import { SchnorrInitializerlessAccountContractArtifact } from "../../artifacts/SchnorrInitializerlessAccount.js";
import {
SchnorrInitializerlessAccountContract as SchnorrInitializerlessAccountContractHandle,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Handle?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overlap of naming, addressed in #10

Copy link
Copy Markdown
Member

@wei3erHase wei3erHase left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only nits, good to continue on test/js imo

Copy link
Copy Markdown
Member

@wei3erHase wei3erHase left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@zkfrov zkfrov merged commit cba9e10 into test/js Feb 20, 2026
6 of 7 checks passed
@zkfrov zkfrov deleted the feat/store-immutables branch February 20, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants