Migration notes
Aztec is in active development. Each version may introduce breaking changes that affect compatibility with previous versions. This page documents common errors and difficulties you might encounter when upgrading, along with guidance on how to resolve them.
TBD
[Aztec.js] Removed SingleKeyAccountContract
The SchnorrSingleKeyAccount contract and its TypeScript wrapper SingleKeyAccountContract have been removed. This contract was insecure: it used ivpk_m (incoming viewing public key) as its Schnorr signing key, meaning anyone who received a user's viewing key could sign transactions on their behalf.
Migration:
- import { SingleKeyAccountContract } from '@aztec/accounts/single_key';
- const contract = new SingleKeyAccountContract(signingKey);
+ import { SchnorrAccountContract } from '@aztec/accounts/schnorr';
+ const contract = new SchnorrAccountContract(signingKey);
Impact: If you were using @aztec/accounts/single_key, switch to @aztec/accounts/schnorr which uses separate keys for encryption and authentication.
aztec new and aztec init now create a 2-crate workspace
aztec new and aztec init now create a workspace with two crates instead of a single contract crate:
- A
contractcrate (type = "contract") for your smart contract code - A
testcrate (type = "lib") for Noir tests, which depends on the contract crate
The new project structure looks like:
my_project/
├── Nargo.toml # [workspace] members = ["contract", "test"]
├── contract/
│ ├── src/main.nr
│ └── Nargo.toml # type = "contract"
└── test/
├── src/lib.nr
└── Nargo.toml # type = "lib"
What changed:
- The
--contractand--libflags have been removed fromaztec newandaztec init. These commands now always create a contract workspace. - Contract code is now at
contract/src/main.nrinstead ofsrc/main.nr. - The
Nargo.tomlin the project root is now a workspace file. Contract dependencies go incontract/Nargo.toml. - Tests should be written in the separate
testcrate (test/src/lib.nr) and import the contract by package name (e.g.,use my_contract::MyContract;) instead of usingcrate::.
Scope enforcement for private state access (TXE and PXE)
Scope enforcement is now active across both TXE (test environment) and PXE (client). Previously, private execution could implicitly access any account's keys and notes. Now, only the caller (from) address is in scope by default, and accessing another address's private state requires explicitly granting scope.
Noir developers (TXE)
TXE now enforces scope isolation, matching PXE behavior. During private execution, only the caller's keys and notes are accessible. If a Noir test accesses private state of an address other than from, it will fail. When from is the zero address, scopes are empty (deny-all).
If your TXE tests fail with key or note access errors, ensure the test is calling from the correct address, or restructure the test to match the expected access pattern.
Aztec.js developers (PXE/Wallet)
The wallet now passes scopes to PXE, and only the from address is in scope by default. Auto-expansion of scopes for nested calls to registered accounts has been removed. A new additionalScopes option is available on send(), simulate(), and deploy() for cases where private execution needs access to another address's keys or notes.
When do you need additionalScopes?
-
Deploying contracts whose constructor initializes private storage (e.g., account contracts, or any contract using
SinglePrivateImmutable/SinglePrivateMutablein the constructor). The contract's own address must be in scope so its nullifier key is accessible during initialization. -
Operations that access another contract's private state (e.g., withdrawing from an escrow contract that nullifies the contract's own token notes).
**Example: deploying a contract with private storage (e.g., `PrivateToken`)**
```diff
const tokenDeployment = PrivateTokenContract.deployWithPublicKeys(
tokenPublicKeys, wallet, initialBalance, sender,
);
const tokenInstance = await tokenDeployment.getInstance();
await wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey);
const token = await tokenDeployment.send({
from: sender,
+ additionalScopes: [tokenInstance.address],
});
Example: withdrawing from an escrow contract
await escrowContract.methods
.withdraw(token.address, amount, recipient)
- .send({ from: owner });
+ .send({ from: owner, additionalScopes: [escrowContract.address] });
simulateUtility renamed to executeUtility
The simulateUtility method and related types have been renamed to executeUtility across the entire stack to better reflect that utility functions are executed, not simulated.
TypeScript:
- import { SimulateUtilityOptions, UtilitySimulationResult } from '@aztec/aztec.js';
+ import { ExecuteUtilityOptions, UtilityExecutionResult } from '@aztec/aztec.js';
- const result: UtilitySimulationResult = await wallet.simulateUtility(functionCall, opts);
+ const result: UtilityExecutionResult = await wallet.executeUtility(functionCall, opts);
Noir (test environment):
- let result = env.simulate_utility(my_contract_address, selector);
+ let result = env.execute_utility(my_contract_address, selector);
4.0.0-devnet.2-patch.0
[Protocol] include_by_timestamp renamed to expiration_timestamp
The include_by_timestamp field has been renamed to expiration_timestamp across the protocol to better convey its meaning.
Noir:
- context.set_tx_include_by_timestamp(123456789);
+ context.set_expiration_timestamp(123456789);
[CLI] Dockerless CLI Installation
The Aztec CLI is now installed without Docker. The installation command has changed:
Old installation (deprecated):
bash -i <(curl -sL https://install.aztec.network)
aztec-up <version>
New installation:
VERSION=<version> bash -i <(curl -sL https://install.aztec.network/<version>)
For example, to install version 5.0.0-nightly.20260302:
VERSION=5.0.0-nightly.20260302 bash -i <(curl -sL https://install.aztec.network/5.0.0-nightly.20260302)
Key changes:
- Docker is no longer required to run the Aztec CLI tools
- The
VERSIONenvironment variable must be set in the installation command - The version must also be included in the URL path
aztec-up is now a version manager:
After installation, aztec-up functions as a version manager with the following commands:
| Command | Description |
|---|---|
aztec-up install <version> | Install a specific version and switch to it |
aztec-up use <version> | Switch to an already installed version |
aztec-up list | List all installed versions |
aztec-up self-update | Update aztec-up itself |
@aztec/test-wallet replaced by @aztec/wallets
The @aztec/test-wallet package has been removed. Use @aztec/wallets instead, which provides EmbeddedWallet with a static create() factory:
- import { TestWallet, registerInitialLocalNetworkAccountsInWallet } from '@aztec/test-wallet/server';
+ import { EmbeddedWallet } from '@aztec/wallets/embedded';
+ import { registerInitialLocalNetworkAccountsInWallet } from '@aztec/wallets/testing';
- const wallet = await TestWallet.create(node);
+ const wallet = await EmbeddedWallet.create(node);
For browser environments, the same import resolves to a browser-specific implementation automatically via conditional exports:X
The EmbeddedWallet.create() factory accepts an optional second argument for logger injection and ephemeral storage:
const wallet = await EmbeddedWallet.create(node, {
logger: myLogger, // custom logger; child loggers derived via createChild()
ephemeral: true, // use in-memory stores (no persistence)
});
[Aztec.nr] debug_log module renamed to logging
The debug_log module has been renamed to logging to avoid naming collisions with per-level logging functions that were introduced in this PR (warn_log, info_log, debug_log... and the "format" versions warn_log_format, debug_log_format). Update all import paths accordingly:
- use aztec::oracle::debug_log::debug_log;
- use aztec::oracle::debug_log::debug_log_format;
+ use aztec::oracle::logging::debug_log;
+ use aztec::oracle::logging::debug_log_format;
For inline paths:
- aztec::oracle::debug_log::debug_log_format("msg: {}", [value]);
+ aztec::oracle::logging::debug_log_format("msg: {}", [value]);
The function names themselves (debug_log, debug_log_format, debug_log_with_level, debug_log_format_with_level) are unchanged.
Additionally, debug_log_format_slice has been removed. Use debug_log_format instead, which accepts a fixed-size array of fields:
- debug_log_format_slice("values: {}", &[value1, value2]);
+ debug_log_format("values: {}", [value1, value2]);
This has been done as usage of Noir slices is discouraged and the function was unused in the aztec codebase.
[AztecNode] Sentinel validator status values renamed
The ValidatorStatusInSlot values returned by getValidatorsStats and getValidatorStats have been updated to reflect the multi-block-per-slot model, where blocks and checkpoints are distinct concepts:
- 'block-mined'
+ 'checkpoint-mined'
- 'block-proposed'
+ 'checkpoint-proposed'
- 'block-missed'
+ 'checkpoint-missed' // blocks were proposed but checkpoint was not attested
+ 'blocks-missed' // no block proposals were sent at all
The attestation-sent and attestation-missed values are unchanged but now explicitly refer to checkpoint attestations.
The ValidatorStatusType used for categorizing statuses has also changed from 'block' | 'attestation' to 'proposer' | 'attestation'.
[aztec.js] getDecodedPublicEvents renamed to getPublicEvents with new signature
The getDecodedPublicEvents function has been renamed to getPublicEvents and now uses a filter object instead of positional parameters:
- import { getDecodedPublicEvents } from '@aztec/aztec.js/events';
+ import { getPublicEvents } from '@aztec/aztec.js/events';
- const events = await getDecodedPublicEvents(node, eventMetadata, fromBlock, limit);
+ const events = await getPublicEvents(node, eventMetadata, {
+ fromBlock,
+ toBlock,
+ contractAddress, // optional
+ txHash, // optional
+ });
The new function returns richer metadata including contractAddress, txHash, l2BlockNumber, and l2BlockHash for each event:
import { getPublicEvents } from "@aztec/aztec.js/events";
import { MyContract } from "./artifacts/MyContract.js";
// Query events from a contract
const events = await getPublicEvents<{ amount: bigint; sender: AztecAddress }>(
aztecNode,
MyContract.events.Transfer,
{ contractAddress: myContractAddress, fromBlock: BlockNumber(1) },
);
// Each event includes decoded data and metadata
for (const { event, metadata } of events) {
console.log(`Transfer of ${event.amount} from ${event.sender}`);
console.log(` Block: ${metadata.l2BlockNumber}, Tx: ${metadata.txHash}`);
console.log(` Contract: ${metadata.contractAddress}`);
}
[Aztec.nr] nophasecheck renamed as allow_phase_change
[AztecNode] Removed sibling path RPC methods
The following methods have been removed from the AztecNode interface:
getNullifierSiblingPathgetNoteHashSiblingPathgetArchiveSiblingPathgetPublicDataSiblingPath
These methods were not used by PXE and returned a subset of the information already available through the corresponding membership witness methods:
| Removed Method | Use Instead |
|---|---|
getNullifierSiblingPath | getNullifierMembershipWitness |
getNoteHashSiblingPath | getNoteHashMembershipWitness |
getArchiveSiblingPath | getBlockHashMembershipWitness |
getPublicDataSiblingPath | getPublicDataWitness |
The membership witness methods return both the sibling path and additional context (leaf index, preimage data) needed for proofs.
[Protocol] "Nullifier secret key" renamed to "nullifier hiding key" (nsk → nhk)
The nullifier secret key (nsk_m / nsk_app) has been renamed to nullifier hiding key (nhk_m / nhk_app). This is a protocol-breaking change: the domain separator string changes from "az_nsk_m" to "az_nhk_m", producing a different constant value.
Noir changes:
- context.request_nsk_app(npk_m_hash)
+ context.request_nhk_app(npk_m_hash)
- get_nsk_app(npk_m_hash)
+ get_nhk_app(npk_m_hash)
TypeScript changes:
- import { computeAppNullifierSecretKey, deriveMasterNullifierSecretKey } from '@aztec/stdlib/keys';
+ import { computeAppNullifierHidingKey, deriveMasterNullifierHidingKey } from '@aztec/stdlib/keys';
- const masterNullifierSecretKey = deriveMasterNullifierSecretKey(secret);
+ const masterNullifierHidingKey = deriveMasterNullifierHidingKey(secret);
- const nskApp = await computeAppNullifierSecretKey(masterNullifierSecretKey, contractAddress);
+ const nhkApp = await computeAppNullifierHidingKey(masterNullifierHidingKey, contractAddress);
The GeneratorIndex.NSK_M enum member is now GeneratorIndex.NHK_M.
[AztecNode/Aztec.nr] getArchiveMembershipWitness renamed to getBlockHashMembershipWitness
The getArchiveMembershipWitness method has been renamed to getBlockHashMembershipWitness to better reflect its purpose. Block hashes are the leaves of the archive tree - each time a new block is added to the chain, its block hash is appended as a new leaf. This rename clarifies that the method finds a membership witness for a block hash in the archive tree.
TypeScript (AztecNode interface):
- const witness = await aztecNode.getArchiveMembershipWitness(blockNumber, archiveLeaf);
+ const witness = await aztecNode.getBlockHashMembershipWitness(blockNumber, blockHash);
The second parameter type has also changed from Fr to BlockHash.
Noir (aztec-nr):
- use dep::aztec::oracle::get_membership_witness::get_archive_membership_witness;
+ use dep::aztec::oracle::get_membership_witness::get_block_hash_membership_witness;
- let witness = get_archive_membership_witness(block_header, leaf_value);
+ let witness = get_block_hash_membership_witness(anchor_block_header, block_hash);
[Aztec.nr] protocol_types renamed to protocol
The protocol_types re-export from the aztec crate has been renamed to protocol. Update all imports accordingly:
- use dep::aztec::protocol_types::address::AztecAddress;
+ use dep::aztec::protocol::address::AztecAddress;
Protocol contract interface separate from protocol contracts
We've stripped protocol contract of aztec-nr macros in order for auditors to not need to audit them (protocol contracts are to be audited during the protocol circuits audit).
This results in the nice Noir interface no longer being generated.
For context, this is the interface I am talking about:
let update_delay = self.view(MyContract::at(my_contract_address).my_fn());
where the macros generate the MyContract struct.
For this reason we've created place holder protocol contracts in noir-projects/noir-contracts/contracts/protocol_interface that still have these macros applied and hence you can use them to get the interface.
On your side all you need to do is update the dependency in Nargo.toml:
-instance_contract = { path = "../../protocol/contract_instance_registry" }
+instance_contract = { path = "../../protocol_interface/contract_instance_registry_interface" }
[aztec-nr] History module refactored to use standalone functions
The aztec::history module has been refactored to use standalone functions instead of traits. This changes the calling convention from method syntax to function syntax.
- use dep::aztec::history::note_inclusion::ProveNoteInclusion;
+ use dep::aztec::history::note::assert_note_existed_by;
let block_header = context.get_anchor_block_header();
- let confirmed_note = block_header.prove_note_inclusion(hinted_note);
+ let confirmed_note = assert_note_existed_by(block_header, hinted_note);
Function name and module mapping:
| Old (trait method) | New (standalone function) |
|---|---|
history::note_inclusion::prove_note_inclusion | history::note::assert_note_existed_by |
history::note_validity::prove_note_validity | history::note::assert_note_was_valid_by |
history::nullifier_inclusion::prove_nullifier_inclusion | history::nullifier::assert_nullifier_existed_by |
history::nullifier_inclusion::prove_note_is_nullified | history::note::assert_note_was_nullified_by |
history::nullifier_non_inclusion::prove_nullifier_non_inclusion | history::nullifier::assert_nullifier_did_not_exist_by |
history::nullifier_non_inclusion::prove_note_not_nullified | history::note::assert_note_was_not_nullified_by |
history::contract_inclusion::prove_contract_deployment | history::deployment::assert_contract_bytecode_was_published_by |
history::contract_inclusion::prove_contract_non_deployment | history::deployment::assert_contract_bytecode_was_not_published_by |
history::contract_inclusion::prove_contract_initialization | history::deployment::assert_contract_was_initialized_by |
history::contract_inclusion::prove_contract_non_initialization | history::deployment::assert_contract_was_not_initialized_by |
history::public_storage::public_storage_historical_read | history::storage::public_storage_historical_read |
[Aztec.js] Transaction sending API redesign
The old chained .send().wait() pattern has been replaced with a single .send(options) call that handles both sending and waiting.
+ import { Contract, NO_WAIT } from '@aztec/aztec.js/contracts';
- const receipt = await contract.methods.transfer(recipient, amount).send().wait();
// Send now waits by default
+ const receipt = await contract.methods.transfer(recipient, amount).send({ from: sender });
// getTxHash() would confusingly send the transaction too
- const txHash = await contract.methods.transfer(recipient, amount).send().getTxHash();
// NO_WAIT to send the transaction and return TxHash immediately
+ const txHash = await contract.methods.transfer(recipient, amount).send({
+ from: sender,
+ wait: NO_WAIT
+ });
Deployment changes
The old .send().deployed() method has been removed. Deployments now return the contract instance by default, or you can request the full receipt with returnReceipt: true:
- const contract = await MyContract.deploy(wallet, ...args).send().deployed();
- const { contract, instance } = await MyContract.deploy(wallet, ...args).send().wait();
+ const contract = await MyContract.deploy(wallet, ...args).send({ from: deployer });
+ const { contract, instance } = await MyContract.deploy(wallet, ...args).send({
+ from: deployer,
+ wait: { returnReceipt: true },
+ });
Breaking changes to Wallet interface
getTxReceipt() has been removed from the interface.
sendTx method signature has changed to support the new wait behavior:
- sendTx(payload: ExecutionPayload, options: SendOptions): Promise<TxReceipt>
+ sendTx<W extends InteractionWaitOptions = undefined>(
+ payload: ExecutionPayload,
+ options: SendOptions<W>
+ ): Promise<SendReturn<W>>
Manual waiting with waitForTx
When using NO_WAIT to send transactions, you can manually wait for confirmation using the waitForTx utility:
import { waitForTx } from "@aztec/aztec.js/node";
const txHash = await contract.methods.transfer(recipient, amount).send({
from: sender,
wait: NO_WAIT,
});
const receipt = await waitForTx(node, txHash, {
timeout: 60000, // Optional: timeout in ms
interval: 1000, // Optional: polling interval in ms
dontThrowOnRevert: true, // Optional: return receipt even if tx reverted
});
[aztec-nr] Removal of intermediate modules
Lots of unnecessary modules have been removed from the API, making imports shorter. These are the modules that contain just a single struct, in which the module has the same name as the struct.
- use aztec::state_vars::private_mutable::PrivateMutable;
+ use aztec::state_vars::PrivateMutable;
Affected structs include all state variables, notes, contexts, messages, etc.
[L1 Contracts] Fee asset pricing direction inverted
The fee model now uses ethPerFeeAsset instead of the previous feeAssetPerEth. This change inverts how the exchange rate is represented: values now express how much ETH one fee asset (AZTEC) is worth, with 1e12 precision.
Key changes:
FeeHeader.feeAssetPerEth→FeeHeader.ethPerFeeAssetRollupConfigInputnow requiresinitialEthPerFeeAssetparameter at deployment- Default value:
1e7(0.00001 ETH per AZTEC) - Valid range:
100(1e-10 ETH/AZTEC) to1e11(0.1 ETH/AZTEC)
New environment variable for node operators:
AZTEC_INITIAL_ETH_PER_FEE_ASSET- Sets the initial ETH per fee asset price with 1e12 precision
[L1 Contracts] Fee asset price modifier now in basis points
The OracleInput.feeAssetPriceModifier field now expects values in basis points (BPS) instead of the previous representation. The modifier is applied as a percentage change to the ETH/AZTEC price each checkpoint.
Key changes:
- Valid range:
-100to+100BPS (±1% max change per checkpoint) - A value of
+100increases the price by 1%,-100decreases by 1% - Validated by
MAX_FEE_ASSET_PRICE_MODIFIER_BPS = 100
[Aztec.js] Wallet batching now supports all methods
The BatchedMethod type is now a discriminated union that ensures type safety: the args must match the specific method name. This prevents runtime errors from mismatched arguments.
- // Before: Only 5 methods could be batched
- const results = await wallet.batch([
- { name: "registerSender", args: [address, "alias"] },
- { name: "sendTx", args: [payload, options] },
- ]);
+ // After: All methods can be batched
+ const results = await wallet.batch([
+ { name: "getChainInfo", args: [] },
+ { name: "getContractMetadata", args: [contractAddress] },
+ { name: "registerSender", args: [address, "alias"] },
+ { name: "simulateTx", args: [payload, options] },
+ { name: "sendTx", args: [payload, options] },
+ ]);
[Aztec.js] Refactored getContractMetadata and getContractClassMetadata in Wallet
The contract metadata methods in the Wallet interface have been refactored to provide more granular information and avoid expensive round-trips.
ContractMetadata:
{
- contractInstance?: ContractInstanceWithAddress,
+ instance?: ContractInstanceWithAddress; // Instance registered in the Wallet, if any
isContractInitialized: boolean; // Is the init nullifier onchain? (already there)
isContractPublished: boolean; // Has the contract been published? (already there)
+ isContractUpdated: boolean; // Has the contract been updated?
+ updatedContractClassId?: Fr; // If updated, the new class ID
}
ContractClassMetadata:
This method loses the ability to request the contract artifact via the includeArtifact flag
{
- contractClass?: ContractClassWithId;
- artifact?: ContractArtifact;
isContractClassPubliclyRegistered: boolean; // Is the class registered onchain?
+ isArtifactRegistered: boolean; // Does the Wallet know about this artifact?
}
- Removes expensive artifact/class transfers between wallet and app
- Separates PXE storage info (
instance,isArtifactRegistered) from public chain info (isContractPublished,isContractClassPubliclyRegistered) - Makes it easier to determine if actions like
registerContractare needed
[Aztec.js] Removed UnsafeContract and protocol contract helper functions
The UnsafeContract class and async helper functions (getFeeJuice, getClassRegistryContract, getInstanceRegistryContract) have been removed. Protocol contracts are now accessed via auto-generated type-safe wrappers with only the ABI (no bytecode). Since PXE always has protocol contract artifacts available, importing and using these contracts from aztec.js is very lightweight and follows the same pattern as regular user contracts.
Migration:
- import { getFeeJuice, getClassRegistryContract, getInstanceRegistryContract } from '@aztec/aztec.js/contracts';
+ import { FeeJuiceContract, ContractClassRegistryContract, ContractInstanceRegistryContract } from '@aztec/aztec.js/protocol';
- const feeJuice = await getFeeJuice(wallet);
+ const feeJuice = FeeJuiceContract.at(wallet);
await feeJuice.methods.check_balance(feeLimit).send().wait();
- const classRegistry = await getClassRegistryContract(wallet);
+ const classRegistry = ContractClassRegistryContract.at(wallet);
await classRegistry.methods.publish(...).send().wait();
- const instanceRegistry = await getInstanceRegistryContract(wallet);
+ const instanceRegistry = ContractInstanceRegistryContract.at(wallet);
await instanceRegistry.methods.publish_for_public_execution(...).send().wait();
Note: The higher-level utilities like publishInstance, publishContractClass, and broadcastPrivateFunction from @aztec/aztec.js/deployment are still available and unchanged. These utilities use the new wrappers internally.
[Aztec.nr] Renamed Router contract
Router contract has been renamed as PublicChecks contract.
The name of the contract became stale as its use changed from routing public calls through it to simply having public functions that can be called by anyone.
Having these "standard checks" on one contract results in a potentially large privacy set for apps that use it.
[Aztec Node] getBlockByHash and getBlockHeaderByHash removed
The getBlockByHash and getBlockHeaderByHash methods have been removed. Use getBlock and getBlockHeader with a block hash instead.
Migration:
- const block = await node.getBlockByHash(blockHash);
+ const block = await node.getBlock(blockHash);
- const header = await node.getBlockHeaderByHash(blockHash);
+ const header = await node.getBlockHeader(blockHash);
[Aztec.nr] Oracle functions now take BlockHeader instead of block number
The low-level oracle functions for fetching membership witnesses and storage now take a BlockHeader instead of a block_number: u32. This change improves type safety and ensures the correct block state is queried.
Affected functions:
get_note_hash_membership_witness(block_header, leaf_value)- was(block_number, leaf_value)get_archive_membership_witness(block_header, leaf_value)- was(block_number, leaf_value)get_nullifier_membership_witness(block_header, nullifier)- was(block_number, nullifier)get_low_nullifier_membership_witness(block_header, nullifier)- was(block_number, nullifier)get_public_data_witness(block_header, public_data_tree_index)- was(block_number, public_data_tree_index)storage_read(block_header, address, storage_slot)- was(address, storage_slot, block_number)
Migration:
If you were calling these oracle functions directly (which is uncommon), update your code to pass a BlockHeader instead of a block number:
- let witness = get_note_hash_membership_witness(self.global_variables.block_number, note_hash);
+ let witness = get_note_hash_membership_witness(self, note_hash);
- let witness = get_nullifier_membership_witness(block_number, nullifier);
+ let witness = get_nullifier_membership_witness(block_header, nullifier);
- let value: T = storage_read(address, slot, block_number);
+ let value: T = storage_read(block_header, address, slot);
Note: The high-level history proof functions on BlockHeader (such as prove_note_inclusion, prove_nullifier_inclusion, etc.) are not affected by this change. They continue to work the same way.
[Toolchain] Node.js upgraded to v24
Node.js minimum version changed from v22 to v24.12.0.
[L1 Contracts] Renamed base fee to min fee
The L1 rollup contract functions and types related to fee calculation have been renamed from "base fee" to "min fee" to better reflect their purpose.
Renamed functions:
getManaBaseFeeAt→getManaMinFeeAtgetManaBaseFeeComponentsAt→getManaMinFeeComponentsAt
Renamed types:
ManaBaseFeeComponents→ManaMinFeeComponents
Renamed errors:
Rollup__InvalidManaBaseFee→Rollup__InvalidManaMinFee
Migration:
- uint256 fee = rollup.getManaBaseFeeAt(timestamp, true);
+ uint256 fee = rollup.getManaMinFeeAt(timestamp, true);
- ManaBaseFeeComponents memory components = rollup.getManaBaseFeeComponentsAt(timestamp, true);
+ ManaMinFeeComponents memory components = rollup.getManaMinFeeComponentsAt(timestamp, true);
[Aztec.js] Renamed base fee to min fee
The Aztec Node API method for getting current fees has been renamed:
getCurrentBaseFees→getCurrentMinFees
Migration:
- const fees = await node.getCurrentBaseFees();
+ const fees = await node.getCurrentMinFees();
[Aztec.nr] Renamed fee context methods
The context methods for accessing fee information have been renamed:
context.base_fee_per_l2_gas()→context.min_fee_per_l2_gas()context.base_fee_per_da_gas()→context.min_fee_per_da_gas()
Migration:
- let l2_fee = context.base_fee_per_l2_gas();
- let da_fee = context.base_fee_per_da_gas();
+ let l2_fee = context.min_fee_per_l2_gas();
+ let da_fee = context.min_fee_per_da_gas();
[Aztec.nr] Cleaning up message sender functions
There has been a design decision made to have low-level API exposed on self.context and a nicer higher-level API exposed directly on self.
Currently the msg_sender function on self was a copy of that same function on self.context.
The msg_sender function on self got modified to return the message sender address directly instead of having it be wrapped in an Option<...>.
In case the underlying message sender is none the function panics.
You need to update your code to no longer trigger the unwrap on the return value:
- let message_sender: AztecAddress = self.msg_sender().unwrap();
+ let message_sender: AztecAddress = self.msg_sender();
If you want to handle the null case use the lower level API of context:
- let maybe_message_sender: Option<AztecAddress> = self.msg_sender();
+ let maybe_message_sender: Option<AztecAddress> = self.context.maybe_msg_sender();
The self.context.msg_sender_unsafe method has been dropped as its use can be replaced with the standard self.context.maybe_msg_sender function.
[Aztec.nr] Renamed message delivery options
The following terms have been renamed:
MessageDelivery::UNCONSTRAINED_OFFCHAIN->MessageDelivery::OFFCHAINMessageDelivery::UNCONSTRAINED_ONCHAIN->MessageDelivery::ONCHAIN_UNCONSTRAINEDMessageDelivery::CONSTRAINED_ONCHAIN->MessageDelivery::ONCHAIN_CONSTRAINED
We believe these names will better convey the meaning of the concepts.
[Aztec Node] changes to getLogsByTags endpoint
getLogsByTags endpoint has been optimized for our new log sync algorithm and these are the changes:
- The
logsPerTagpagination argument has been removed. Pagination was unnecessary here, since multiple logs per tag typically only occur if several devices are sending logs from the same sender to a recipient, which is unlikely to generate enough logs to require pagination. - The structure of
TxScopedL2Loghas been revised to meet the requirements of our new log sync algorithm. - The endpoint has been separated into two versions:
getPrivateLogsByTagsandgetPublicLogsByTagsFromContract. This change was made because it was never desirable in PXE to mix public and private logs. The public version requires both aTagand a contract address as input. In contrast to the private version—which usesSiloedTag(a tag that hashes the raw tag with the emitting contract's address)—the public version uses the rawTagtype, since kernels do not hash the tag with the contract address for public logs.
[AVM] Gas cost multipliers for public execution to reach simulation/proving parity
Gas costs for several AVM opcodes have been adjusted with multipliers to better align public simulation costs with actual proving costs.
| Opcode | Multiplier | Previous Cost | New Cost |
|---|---|---|---|
| FDIV | 25x | 9 | 225 |
| SLOAD | 10x | 129 | 1,290 |
| SSTORE | 20x | 1,657 | 33,140 |
| NOTEHASHEXISTS | 4x | 126 | 504 |
| EMITNOTEHASH | 15x | 1,285 | 19,275 |
| NULLIFIEREXISTS | 7x | 132 | 924 |
| EMITNULLIFIER | 20x | 1,540 | 30,800 |
| L1TOL2MSGEXISTS | 5x | 108 | 540 |
| SENDL2TOL1MSG | 2x | 209 | 418 |
| CALL | 3x | 3,312 | 9,936 |
| STATICCALL | 3x | 3,312 | 9,936 |
| GETCONTRACTINSTANCE | 4x | 1,527 | 6,108 |
| POSEIDON2 | 15x | 24 | 360 |
| ECADD | 10x | 27 | 270 |
Impact: Contracts with public bytecode performing any of these operations will see increased gas consumption.
[PXE] deprecated getNotes
This function serves only for debugging purposes so we are taking it out of the main PXE API. If you still need to consume it, you can
do so through the new debug sub-module.
- this.pxe.getNotes(filter);
+ this.pxe.debug.getNotes(filter);
3.0.0-devnet.20251212
[Aztec node, archiver] Deprecated getPrivateLogs
Aztec node no longer offers a getPrivateLogs method. If you need to process the logs of a block, you can instead use getBlock and call getPrivateLogs on an L2BlockNew instance. See the diff below for before/after equivalent code samples.
- const logs = await aztecNode.getPrivateLogs(blockNumber, 1);
+ const logs = (await aztecNode.getBlock(blockNumber))?.toL2Block().getPrivateLogs();
[Aztec.nr] Private event emission API changes
Private events are still emitted via the emit function, but this now returns an EventMessage type that must have deliver_to called on it in order to deliver the event message to the intended recipients. This allows for multiple recipients to receive the same event.
- self.emit(event, recipient, delivery_method)
+ self.emit(event).delivery(recipient, delivery_method)
[Aztec.nr] History proof functions no longer require storage_slot parameter
The HintedNote struct now includes a storage_slot field, making it self-contained for proving note inclusion and validity. As a result, the history proof functions in the aztec::history module no longer require a separate storage_slot parameter.
Affected functions:
BlockHeader::prove_note_inclusion- removedstorage_slot: FieldparameterBlockHeader::prove_note_validity- removedstorage_slot: FieldparameterBlockHeader::prove_note_is_nullified- removedstorage_slot: FieldparameterBlockHeader::prove_note_not_nullified- removedstorage_slot: Fieldparameter
Migration:
The storage_slot is now read from hinted_note.storage_slot internally. Simply remove the storage_slot argument from all calls to these functions:
let header = context.get_anchor_block_header();
- header.prove_note_inclusion(hinted_note, storage_slot);
+ header.prove_note_inclusion(hinted_note);
let header = context.get_anchor_block_header();
- header.prove_note_validity(hinted_note, storage_slot, context);
+ header.prove_note_validity(hinted_note, context);
let header = context.get_anchor_block_header();
- header.prove_note_is_nullified(hinted_note, storage_slot, context);
+ header.prove_note_is_nullified(hinted_note, context);
let header = context.get_anchor_block_header();
- header.prove_note_not_nullified(hinted_note, storage_slot, context);
+ header.prove_note_not_nullified(hinted_note, context);
[Aztec.nr] Note fields are now public
All note struct fields are now public, and the new() constructor methods and getter methods have been removed. Notes should be instantiated using struct literal syntax, and fields should be accessed directly.
The motivation for this change has been enshrining of randomness which lead to the new method being unnecessary boilerplate.
Affected notes:
UintNote-valueis now public,new()andget_value()removedAddressNote-addressis now public,new()andget_address()removedFieldNote-valueis now public,new()andvalue()removed
Migration:
- let note = UintNote::new(100);
+ let note = UintNote { value: 100 };
- let value = note.get_value();
+ let value = note.value;
- let address_note = AddressNote::new(owner);
+ let address_note = AddressNote { address: owner };
- let address = address_note.get_address();
+ let address = address_note.address;
- let field_note = FieldNote::new(42);
+ let field_note = FieldNote { value: 42 };
- let value = field_note.value();
+ let value = field_note.value;
[Aztec.nr] emit renamed to deliver
Private state variable functions that created notes and returned their messages no longer return a NoteEmission but instead a NoteMessage. These messages are delivered to their owner via deliver instead of emit. The verb 'emit' remains for things like emitting events.
- self.storage.balances.at(owner).add(5).emit(owner);
+ self.storage.balances.at(owner).add(5).deliver();
To deliver a message to a different recipient, use deliver_to:
- self.storage.balances.at(owner).add(5).emit(other);
+ self.storage.balances.at(owner).add(5).deliver_to(other);
[Aztec.nr] ValueNote renamed to FieldNote and value-note crate renamed to field-note
The ValueNote struct has been renamed to FieldNote to better reflect that it stores a Field value. The crate has also been renamed from value-note to field-note.
Migration:
- Update your
Nargo.tomldependencies:value_note = { path = "..." }→field_note = { path = "..." } - Update imports:
use value_note::value_note::ValueNote→use field_note::field_note::FieldNote - Update type references:
ValueNote→FieldNote - Update generic parameters:
PrivateSet<ValueNote, ...>→PrivateSet<FieldNote, ...>
[Aztec.nr] New balance-set library for managing token balances
A new balance-set library has been created that provides BalanceSet<Context> for managing u128 token balances with UintNote. This consolidates balance management functionality that was previously duplicated across contracts.
Features:
add(amount: u128)- Add to balancesub(amount: u128)- Subtract from balance (with change note)try_sub(amount: u128, max_notes: u32)- Attempt to subtract with configurable note limitbalance_of()- Get total balance (unconstrained)
Usage:
use balance_set::BalanceSet;
#[storage]
struct Storage<Context> {
balances: Owned<BalanceSet<Context>, Context>,
}
// In a private function:
self.storage.balances.at(owner).add(amount).deliver(owner, MessageDelivery.CONSTRAINED_ONCHAIN);
self.storage.balances.at(owner).sub(amount).deliver(owner, MessageDelivery.CONSTRAINED_ONCHAIN);
// In an unconstrained function:
let balance = self.storage.balances.at(owner).balance_of();