Aztec Contract Development
Overview
Use this skill for Aztec.nr contract work inside the pinned aztec-packages checkout.
Primary scope:
- Contract structure and storage design
- Private/public/utility function design
- Note and private-event delivery mode selection
- Authwit-protected delegated actions
- Compile, test, codegen, debugging, and upgrade flows
Required Repository State
Use the upstream repository and pin:
- Repo:
https://github.com/AztecProtocol/aztec-packages
- Tag:
v4.2.0
- Commit:
f8c89cf4345df6c4ca9e66ea9b738e96070abc5a
Checkout example:
git clone https://github.com/AztecProtocol/aztec-packages.git
cd aztec-packages
git checkout v4.2.0
git status
Expected status includes HEAD detached at v4.2.0.
Operating Rules
- Keep implementation aligned with the pinned source set:
docs/docs-developers/docs/aztec-nr/**
docs/docs-developers/docs/foundational-topics/contract_creation.md
docs/docs-developers/docs/tutorials/contract_tutorials/**
- Use GitHub paths when needed:
https://github.com/AztecProtocol/aztec-packages/tree/v4.2.0/docs/docs-developers/docs/aztec-nr
- Prefer protocol-correct behavior over stylistic churn.
- Keep function intent explicit: execution domain, state domain, and call path.
- Never leave note/event delivery implicit.
- Keep
SKILL.md procedural; put deep detail in reference.md and patterns.md.
Quick Start
# from a contract crate
aztec compile
aztec test
aztec codegen target --outdir artifacts
Use scripts in scripts/ for the same flows:
scripts/new_contract.sh
scripts/build_contract.sh
scripts/test_contract.sh
scripts/codegen_contract.sh
Core Workflows
1. Contract Scaffold and Structure
- One contract per Noir crate.
- Apply
#[aztec] to every contract.
- Keep
#[external(...)] entrypoints directly in the contract block.
- Define one
#[storage] struct with Storage<Context>.
- Put helper/internal logic in internal functions or modules, not external entrypoints.
Baseline pattern:
use aztec::macros::aztec;
#[aztec]
pub contract MyContract {
use aztec::{
macros::{functions::{external, initializer, view}, storage::storage},
protocol::address::AztecAddress,
state_vars::{PublicImmutable, PublicMutable},
};
#[storage]
struct Storage<Context> {
admin: PublicImmutable<AztecAddress, Context>,
value: PublicMutable<Field, Context>,
}
#[initializer]
#[external("public")]
fn constructor(admin: AztecAddress) {
self.storage.admin.initialize(admin);
self.storage.value.write(0);
}
#[external("public")]
fn set_value(value: Field) {
assert(self.msg_sender() == self.storage.admin.read(), "not admin");
self.storage.value.write(value);
}
#[view]
#[external("public")]
fn get_value() -> Field {
self.storage.value.read()
}
}
2. State Modeling
- Use
PublicMutable for mutable public state.
- Use
PublicImmutable for write-once values readable across contexts.
- Use
DelayedPublicMutable when private execution must read a public mutable value.
- Use
Owned<PrivateMutable<...>> and Owned<PrivateSet<...>> for per-owner private state.
- Use
SinglePrivateMutable/SinglePrivateImmutable for contract-wide private singleton state.
- Use
Map<K, ...> for keyed public layouts.
3. Function Domain Design
#[external("private")]: private execution and private-state workflows.
#[external("public")]: sequencer-executed public-state workflows.
#[external("utility")] unconstrained: offchain query/helper logic.
#[view]: read-only private/public external functions.
#[initializer]: constructor-like setup.
#[noinitcheck]: only where pre-init calls are explicitly needed. (Do not confuse with #[allow_phase_change], which is a separate phase-check override and is not a replacement for #[noinitcheck].)
#[only_self]: public/private functions only callable by the same contract. In v4.2.0, #[only_self] implicitly skips the init check (it behaves as if also marked #[noinitcheck]), which means external functions invoked during a private initializer must be #[only_self].
#[authorize_once("from", "authwit_nonce")]: delegated actions with replay protection.
4. Note/Event Delivery
Whenever a state write or private event returns a message object:
- Pick delivery mode explicitly.
- Call
.deliver(...) or .deliver_to(...).
Delivery selection:
MessageDelivery.OFFCHAIN: cheapest; sender must manually deliver/process offchain messages.
MessageDelivery.ONCHAIN_UNCONSTRAINED: onchain availability, no constrained correctness.
MessageDelivery.ONCHAIN_CONSTRAINED: strongest available guarantees; highest proving cost.
Known caveat in pinned docs/code:
ONCHAIN_CONSTRAINED currently has an acknowledged tag-constraining limitation (issue #14565). Use it when trust assumptions require it, but do not claim perfect guarantees.
5. Cross-Contract and Private->Public Flows
- Use
self.call(Other::at(addr).fn(...)) for direct same-domain external calls.
- Use
self.view(...) for read-only external calls.
- Use
self.enqueue(...) for private->public deferred execution.
- Use
self.enqueue_self.some_public_fn(...) plus #[only_self] when private logic must safely update public state.
- Do not rely on return values from enqueued public calls in private execution.
6. Initialization and Readiness
- Initialization and public deployment are distinct concerns.
- Contracts with public entrypoints usually require class registration plus instance public deployment for public calls.
- Private-only contracts can often operate without public deployment.
without_initializer() plus no #[initializer] means initialized-by-design at deploy time.
#[noinitcheck] functions may be callable before initialization. #[only_self] functions also implicitly skip the init check in v4.2.0.
- v4.2.0 emits two separate initialization nullifiers (one private, one public). Contracts that expose public functions alongside a private initializer cannot use
skipClassPublication: true at deploy time, because the public init nullifier is emitted via an auto-enqueued public call that requires the class to be published onchain.
7. Authwit
- Use authwit when acting on behalf of another account/owner.
- Prefer
#[authorize_once] for straightforward delegated call checks.
- Treat nonce handling as mandatory replay protection.
- Use contract accounts in tests when validating authwit workflows.
- Always include negative-path tests (missing/invalid/cancelled authwit).
8. Compile, Test, Codegen
Default sequence:
aztec compile
aztec test
aztec codegen target --outdir artifacts (when TS integration is involved)
Rules:
- Use
aztec test (not nargo test) for Aztec contract behavior.
- Recompile before tests because
aztec test does not auto-recompile changed contracts.
- Treat
target/*.json as canonical contract artifacts.
9. Upgrades
- Initiate upgrades through
ContractInstanceRegistry.update(new_class_id).
- Protect upgrade entrypoints with strict authorization.
- Respect delayed activation semantics (
DelayedPublicMutable-based delay).
- Preserve storage compatibility across versions.
- Re-register updated artifacts in wallets/clients after delay elapses.
Tooling / Commands
# Local network debugging
LOG_LEVEL=debug aztec start --local-network
# Compile and test
aztec compile
aztec test
# Generate TypeScript bindings
aztec codegen target --outdir artifacts
# Profile private flows
aztec-wallet profile <function_name> -ca <contract_alias> --args [...] -f <account>
# Gate count report
noir-profiler gates --artifact-path ./target/<artifact>.json --backend-path bb --output ./target
Gate-count guidance in pinned docs:
< 50k: excellent
50k-200k: acceptable
200k-500k: consider optimizing
> 500k: optimize before scaling
Edge Cases and Failure Handling
aztec test failure after edits: rerun aztec compile first.
- Public function callable unexpectedly: confirm
#[only_self] where private->public enqueueing is expected.
- Note recipient cannot use note: verify delivery mode and delivery call were executed.
- Missing authwit behavior in tests: switch to contract accounts and add authwit helper setup.
- Pre-init behavior mismatch: audit
#[initializer], #[noinitcheck], and deployment path.
- Upgrade appears ineffective: verify delay elapsed and new artifact registered in wallet/client.
- Utility function misuse: ensure utility functions are only used for unconstrained/offchain workflows.
Next Steps / Related Files
- Use
reference.md for pinned source map, implementation matrix, and troubleshooting checklists.
- Use
patterns.md for reusable code patterns.
- Use scripts in
scripts/ for scaffold/build/test/codegen flows.
1---2name: aztec-contracts3description: Use this skill when creating, editing, testing, debugging, or upgrading Aztec smart contracts in Noir/Aztec.nr, including storage modeling, private/public/utility functions, note delivery, authwit authorization, TestEnvironment tests, and artifact/codegen workflows.4license: Proprietary. LICENSE.txt has complete terms5---67# Aztec Contract Development89## Overview1011Use this skill for Aztec.nr contract work inside the pinned `aztec-packages` checkout.1213Primary scope:1415- Contract structure and storage design16- Private/public/utility function design17- Note and private-event delivery mode selection18- Authwit-protected delegated actions19- Compile, test, codegen, debugging, and upgrade flows2021## Required Repository State2223Use the upstream repository and pin:2425- Repo: `https://github.com/AztecProtocol/aztec-packages`26- Tag: `v4.2.0`27- Commit: `f8c89cf4345df6c4ca9e66ea9b738e96070abc5a`2829Checkout example:3031```bash32git clone https://github.com/AztecProtocol/aztec-packages.git33cd aztec-packages34git checkout v4.2.035git status36```3738Expected status includes `HEAD detached at v4.2.0`.3940## Operating Rules4142- Keep implementation aligned with the pinned source set:43- `docs/docs-developers/docs/aztec-nr/**`44- `docs/docs-developers/docs/foundational-topics/contract_creation.md`45- `docs/docs-developers/docs/tutorials/contract_tutorials/**`46- Use GitHub paths when needed:47- `https://github.com/AztecProtocol/aztec-packages/tree/v4.2.0/docs/docs-developers/docs/aztec-nr`48- Prefer protocol-correct behavior over stylistic churn.49- Keep function intent explicit: execution domain, state domain, and call path.50- Never leave note/event delivery implicit.51- Keep `SKILL.md` procedural; put deep detail in `reference.md` and `patterns.md`.5253## Quick Start5455```bash56# from a contract crate57aztec compile58aztec test59aztec codegen target --outdir artifacts60```6162Use scripts in `scripts/` for the same flows:6364- `scripts/new_contract.sh`65- `scripts/build_contract.sh`66- `scripts/test_contract.sh`67- `scripts/codegen_contract.sh`6869## Core Workflows7071### 1. Contract Scaffold and Structure7273- One contract per Noir crate.74- Apply `#[aztec]` to every contract.75- Keep `#[external(...)]` entrypoints directly in the contract block.76- Define one `#[storage]` struct with `Storage<Context>`.77- Put helper/internal logic in internal functions or modules, not external entrypoints.7879Baseline pattern:8081```rust82use aztec::macros::aztec;8384#[aztec]85pub contract MyContract {86 use aztec::{87 macros::{functions::{external, initializer, view}, storage::storage},88 protocol::address::AztecAddress,89 state_vars::{PublicImmutable, PublicMutable},90 };9192 #[storage]93 struct Storage<Context> {94 admin: PublicImmutable<AztecAddress, Context>,95 value: PublicMutable<Field, Context>,96 }9798 #[initializer]99 #[external("public")]100 fn constructor(admin: AztecAddress) {101 self.storage.admin.initialize(admin);102 self.storage.value.write(0);103 }104105 #[external("public")]106 fn set_value(value: Field) {107 assert(self.msg_sender() == self.storage.admin.read(), "not admin");108 self.storage.value.write(value);109 }110111 #[view]112 #[external("public")]113 fn get_value() -> Field {114 self.storage.value.read()115 }116}117```118119### 2. State Modeling120121- Use `PublicMutable` for mutable public state.122- Use `PublicImmutable` for write-once values readable across contexts.123- Use `DelayedPublicMutable` when private execution must read a public mutable value.124- Use `Owned<PrivateMutable<...>>` and `Owned<PrivateSet<...>>` for per-owner private state.125- Use `SinglePrivateMutable`/`SinglePrivateImmutable` for contract-wide private singleton state.126- Use `Map<K, ...>` for keyed public layouts.127128### 3. Function Domain Design129130- `#[external("private")]`: private execution and private-state workflows.131- `#[external("public")]`: sequencer-executed public-state workflows.132- `#[external("utility")] unconstrained`: offchain query/helper logic.133- `#[view]`: read-only private/public external functions.134- `#[initializer]`: constructor-like setup.135- `#[noinitcheck]`: only where pre-init calls are explicitly needed. (Do not confuse with `#[allow_phase_change]`, which is a separate phase-check override and is not a replacement for `#[noinitcheck]`.)136- `#[only_self]`: public/private functions only callable by the same contract. In v4.2.0, `#[only_self]` implicitly skips the init check (it behaves as if also marked `#[noinitcheck]`), which means external functions invoked during a private initializer **must** be `#[only_self]`.137- `#[authorize_once("from", "authwit_nonce")]`: delegated actions with replay protection.138139### 4. Note/Event Delivery140141Whenever a state write or private event returns a message object:1421431. Pick delivery mode explicitly.1442. Call `.deliver(...)` or `.deliver_to(...)`.145146Delivery selection:147148- `MessageDelivery.OFFCHAIN`: cheapest; sender must manually deliver/process offchain messages.149- `MessageDelivery.ONCHAIN_UNCONSTRAINED`: onchain availability, no constrained correctness.150- `MessageDelivery.ONCHAIN_CONSTRAINED`: strongest available guarantees; highest proving cost.151152Known caveat in pinned docs/code:153154- `ONCHAIN_CONSTRAINED` currently has an acknowledged tag-constraining limitation (issue #14565). Use it when trust assumptions require it, but do not claim perfect guarantees.155156### 5. Cross-Contract and Private->Public Flows157158- Use `self.call(Other::at(addr).fn(...))` for direct same-domain external calls.159- Use `self.view(...)` for read-only external calls.160- Use `self.enqueue(...)` for private->public deferred execution.161- Use `self.enqueue_self.some_public_fn(...)` plus `#[only_self]` when private logic must safely update public state.162- Do not rely on return values from enqueued public calls in private execution.163164### 6. Initialization and Readiness165166- Initialization and public deployment are distinct concerns.167- Contracts with public entrypoints usually require class registration plus instance public deployment for public calls.168- Private-only contracts can often operate without public deployment.169- `without_initializer()` plus no `#[initializer]` means initialized-by-design at deploy time.170- `#[noinitcheck]` functions may be callable before initialization. `#[only_self]` functions also implicitly skip the init check in v4.2.0.171- v4.2.0 emits two separate initialization nullifiers (one private, one public). Contracts that expose public functions alongside a private initializer cannot use `skipClassPublication: true` at deploy time, because the public init nullifier is emitted via an auto-enqueued public call that requires the class to be published onchain.172173### 7. Authwit174175- Use authwit when acting on behalf of another account/owner.176- Prefer `#[authorize_once]` for straightforward delegated call checks.177- Treat nonce handling as mandatory replay protection.178- Use contract accounts in tests when validating authwit workflows.179- Always include negative-path tests (missing/invalid/cancelled authwit).180181### 8. Compile, Test, Codegen182183Default sequence:1841851. `aztec compile`1862. `aztec test`1873. `aztec codegen target --outdir artifacts` (when TS integration is involved)188189Rules:190191- Use `aztec test` (not `nargo test`) for Aztec contract behavior.192- Recompile before tests because `aztec test` does not auto-recompile changed contracts.193- Treat `target/*.json` as canonical contract artifacts.194195### 9. Upgrades196197- Initiate upgrades through `ContractInstanceRegistry.update(new_class_id)`.198- Protect upgrade entrypoints with strict authorization.199- Respect delayed activation semantics (`DelayedPublicMutable`-based delay).200- Preserve storage compatibility across versions.201- Re-register updated artifacts in wallets/clients after delay elapses.202203## Tooling / Commands204205```bash206# Local network debugging207LOG_LEVEL=debug aztec start --local-network208209# Compile and test210aztec compile211aztec test212213# Generate TypeScript bindings214aztec codegen target --outdir artifacts215216# Profile private flows217aztec-wallet profile <function_name> -ca <contract_alias> --args [...] -f <account>218219# Gate count report220noir-profiler gates --artifact-path ./target/<artifact>.json --backend-path bb --output ./target221```222223Gate-count guidance in pinned docs:224225- `< 50k`: excellent226- `50k-200k`: acceptable227- `200k-500k`: consider optimizing228- `> 500k`: optimize before scaling229230## Edge Cases and Failure Handling231232- `aztec test` failure after edits: rerun `aztec compile` first.233- Public function callable unexpectedly: confirm `#[only_self]` where private->public enqueueing is expected.234- Note recipient cannot use note: verify delivery mode and delivery call were executed.235- Missing authwit behavior in tests: switch to contract accounts and add authwit helper setup.236- Pre-init behavior mismatch: audit `#[initializer]`, `#[noinitcheck]`, and deployment path.237- Upgrade appears ineffective: verify delay elapsed and new artifact registered in wallet/client.238- Utility function misuse: ensure utility functions are only used for unconstrained/offchain workflows.239240## Next Steps / Related Files241242- Use `reference.md` for pinned source map, implementation matrix, and troubleshooting checklists.243- Use `patterns.md` for reusable code patterns.244- Use scripts in `scripts/` for scaffold/build/test/codegen flows.